payload 4.0.0-beta.2 → 4.0.0-beta.4

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 (122) hide show
  1. package/dist/admin/adapters/router.d.ts +1 -1
  2. package/dist/admin/adapters/router.d.ts.map +1 -1
  3. package/dist/admin/adapters/router.js.map +1 -1
  4. package/dist/admin/initAdminContext.js.map +1 -1
  5. package/dist/auth/getFieldsToSign.js.map +1 -1
  6. package/dist/auth/loginWithCookie.js.map +1 -1
  7. package/dist/auth/serverFunctions/cookies.js.map +1 -1
  8. package/dist/auth/serverFunctions/login.js.map +1 -1
  9. package/dist/auth/types.d.ts.map +1 -1
  10. package/dist/auth/types.js.map +1 -1
  11. package/dist/cli/commands/data/utilities.d.ts +1 -1
  12. package/dist/cli/commands/data/utilities.d.ts.map +1 -1
  13. package/dist/cli/commands/data/utilities.js.map +1 -1
  14. package/dist/cli/defineCLICommand.js +1 -1
  15. package/dist/cli/defineCLICommand.js.map +1 -1
  16. package/dist/collections/config/client.js.map +1 -1
  17. package/dist/collections/config/types.d.ts.map +1 -1
  18. package/dist/collections/config/types.js.map +1 -1
  19. package/dist/collections/operations/findByID.js.map +1 -1
  20. package/dist/collections/operations/findVersionByID.js.map +1 -1
  21. package/dist/collections/operations/findVersions.js.map +1 -1
  22. package/dist/collections/operations/local/findDistinct.js.map +1 -1
  23. package/dist/collections/operations/renameFile.js +1 -1
  24. package/dist/collections/operations/renameFile.js.map +1 -1
  25. package/dist/collections/operations/utilities/update.js +1 -1
  26. package/dist/collections/operations/utilities/update.js.map +1 -1
  27. package/dist/collections/operations/validate.js +4 -4
  28. package/dist/collections/operations/validate.js.map +1 -1
  29. package/dist/config/definePlugin.js.map +1 -1
  30. package/dist/config/sanitize.js +2 -2
  31. package/dist/config/sanitize.js.map +1 -1
  32. package/dist/config/types.d.ts.map +1 -1
  33. package/dist/config/types.js.map +1 -1
  34. package/dist/database/types.d.ts +4 -0
  35. package/dist/database/types.d.ts.map +1 -1
  36. package/dist/database/types.js.map +1 -1
  37. package/dist/exports/build-config.d.ts +9 -0
  38. package/dist/exports/build-config.d.ts.map +1 -0
  39. package/dist/exports/build-config.js +38 -0
  40. package/dist/exports/build-config.js.map +1 -0
  41. package/dist/fields/config/client.js.map +1 -1
  42. package/dist/fields/config/reservedFieldNames.d.ts.map +1 -1
  43. package/dist/fields/config/reservedFieldNames.js.map +1 -1
  44. package/dist/fields/config/types.d.ts.map +1 -1
  45. package/dist/fields/config/types.js.map +1 -1
  46. package/dist/fields/hooks/afterChange/promise.js.map +1 -1
  47. package/dist/fields/hooks/afterRead/promise.js.map +1 -1
  48. package/dist/fields/hooks/beforeChange/flattenValidationData.d.ts +14 -0
  49. package/dist/fields/hooks/beforeChange/flattenValidationData.d.ts.map +1 -0
  50. package/dist/fields/hooks/beforeChange/flattenValidationData.js +36 -0
  51. package/dist/fields/hooks/beforeChange/flattenValidationData.js.map +1 -0
  52. package/dist/fields/hooks/beforeChange/promise.d.ts +2 -1
  53. package/dist/fields/hooks/beforeChange/promise.d.ts.map +1 -1
  54. package/dist/fields/hooks/beforeChange/promise.js +96 -22
  55. package/dist/fields/hooks/beforeChange/promise.js.map +1 -1
  56. package/dist/fields/hooks/beforeChange/traverseFields.d.ts +2 -1
  57. package/dist/fields/hooks/beforeChange/traverseFields.d.ts.map +1 -1
  58. package/dist/fields/hooks/beforeChange/traverseFields.js +2 -1
  59. package/dist/fields/hooks/beforeChange/traverseFields.js.map +1 -1
  60. package/dist/fields/validations.d.ts.map +1 -1
  61. package/dist/fields/validations.js +4 -1
  62. package/dist/fields/validations.js.map +1 -1
  63. package/dist/globals/config/types.d.ts.map +1 -1
  64. package/dist/globals/config/types.js.map +1 -1
  65. package/dist/globals/operations/local/findOne.js.map +1 -1
  66. package/dist/globals/operations/local/update.js.map +1 -1
  67. package/dist/globals/operations/restoreVersion.d.ts +2 -3
  68. package/dist/globals/operations/restoreVersion.d.ts.map +1 -1
  69. package/dist/globals/operations/restoreVersion.js +22 -15
  70. package/dist/globals/operations/restoreVersion.js.map +1 -1
  71. package/dist/globals/operations/validate.js +1 -1
  72. package/dist/globals/operations/validate.js.map +1 -1
  73. package/dist/hierarchy/createFolderField.js.map +1 -1
  74. package/dist/hierarchy/resolveHierarchyCollections.js +2 -2
  75. package/dist/hierarchy/resolveHierarchyCollections.js.map +1 -1
  76. package/dist/hierarchy/utils/computePaths.d.ts.map +1 -1
  77. package/dist/hierarchy/utils/computePaths.js.map +1 -1
  78. package/dist/index.bundled.d.ts +18 -4
  79. package/dist/queues/operations/runJobs/runJob/getRunTaskFunction.js +1 -1
  80. package/dist/queues/operations/runJobs/runJob/getRunTaskFunction.js.map +1 -1
  81. package/dist/types/index.d.ts.map +1 -1
  82. package/dist/types/index.js.map +1 -1
  83. package/dist/uploads/fileVersioning/archive.js.map +1 -1
  84. package/dist/uploads/fileVersioning/cloudStorage.js.map +1 -1
  85. package/dist/uploads/fileVersioning/resolveHistoricalFile.js.map +1 -1
  86. package/dist/uploads/generateFileData.d.ts.map +1 -1
  87. package/dist/uploads/generateFileData.js.map +1 -1
  88. package/dist/uploads/getFileContentRequirement.js.map +1 -1
  89. package/dist/uploads/getFileFromUploadInstructions.js.map +1 -1
  90. package/dist/uploads/safeFetch.js.map +1 -1
  91. package/dist/uploads/stagedUpload.js.map +1 -1
  92. package/dist/uploads/transformers/handleDynamicFileRequest.js +11 -2
  93. package/dist/uploads/transformers/handleDynamicFileRequest.js.map +1 -1
  94. package/dist/utilities/addSelectGenericsToGeneretedTypes.d.ts.map +1 -1
  95. package/dist/utilities/addSelectGenericsToGeneretedTypes.js +0 -1
  96. package/dist/utilities/addSelectGenericsToGeneretedTypes.js.map +1 -1
  97. package/dist/utilities/appendDateTimezoneSelectFields.js.map +1 -1
  98. package/dist/utilities/cloneValidationRequest.js.map +1 -1
  99. package/dist/utilities/createPayloadRequest.js.map +1 -1
  100. package/dist/utilities/deepCopyObject.js.map +1 -1
  101. package/dist/utilities/documentMatchingWhereExists.js +1 -1
  102. package/dist/utilities/documentMatchingWhereExists.js.map +1 -1
  103. package/dist/utilities/entityInputSchema/validateEntityData.d.ts.map +1 -1
  104. package/dist/utilities/entityInputSchema/validateEntityData.js.map +1 -1
  105. package/dist/utilities/filterDataToSelectedLocales.js.map +1 -1
  106. package/dist/utilities/getEntityPermissions/getEntityPermissions.d.ts.map +1 -1
  107. package/dist/utilities/getEntityPermissions/getEntityPermissions.js +1 -0
  108. package/dist/utilities/getEntityPermissions/getEntityPermissions.js.map +1 -1
  109. package/dist/utilities/getEntityPermissions/populateFieldPermissions.js.map +1 -1
  110. package/dist/utilities/getVersionsConfig.js.map +1 -1
  111. package/dist/utilities/parseParams/index.js.map +1 -1
  112. package/dist/utilities/parseValidationLocale.js.map +1 -1
  113. package/dist/utilities/sanitizePopulateParam.js.map +1 -1
  114. package/dist/utilities/telemetry/conf/index.d.ts +4 -1
  115. package/dist/utilities/telemetry/conf/index.d.ts.map +1 -1
  116. package/dist/utilities/telemetry/conf/index.js.map +1 -1
  117. package/dist/utilities/telemetry/featureInfo/getFeatureInfo.js.map +1 -1
  118. package/dist/versions/allLocalesPublicationStatus.js.map +1 -1
  119. package/dist/versions/isInheritedReadVersionsAccess.js.map +1 -1
  120. package/package.json +29 -27
  121. package/skills/payload/reference/ENDPOINTS.md +3 -9
  122. package/skills/payload/reference/PLUGIN-DEVELOPMENT.md +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/uploads/getFileFromUploadInstructions.ts"],"sourcesContent":["import type { ReadableStream } from 'node:stream/web'\n\nimport { randomUUID } from 'node:crypto'\nimport fs from 'node:fs'\nimport { mkdir, rm, stat } from 'node:fs/promises'\nimport os from 'node:os'\nimport path from 'node:path'\nimport { Readable } from 'node:stream'\nimport { pipeline } from 'node:stream/promises'\n\nimport type { PayloadRequest } from '../types/index.js'\nimport type { SanitizedUploadConfig, UploadEdits, UploadInstructions } from './types.js'\n\nimport { APIError } from '../errors/APIError.js'\nimport { sanitizeFilename } from '../utilities/sanitizeFilename.js'\nimport { sanitizeUploadPrefix } from '../utilities/sanitizeUploadPrefix.js'\nimport { verifyClientUploadReceipt } from './clientUploadReceipt.js'\nimport { docWithFilenameExists } from './docWithFilenameExists.js'\nimport { getOriginalFilename, normalizeStorageKey } from './fileVersioning/naming.js'\nimport { getFileContentRequirement, HEADER_PROBE_BYTE_LENGTH } from './getFileContentRequirement.js'\nimport { getImageSize } from './getImageSize.js'\nimport { hasCropOrResizeEdit } from './hasCropOrResizeEdit.js'\nimport { getStagedFile } from './stagedUpload.js'\nimport { planTransformerPipeline } from './transformers/planTransformerPipeline.js'\nimport { getUploadTransformerInternal } from './transformers/uploadTransformerBridge.js'\n\nexport const getFileFromUploadInstructions = async ({\n collectionSlug,\n file,\n req,\n}: {\n collectionSlug: string\n file: UploadInstructions['file']\n req: PayloadRequest\n}): Promise<NonNullable<PayloadRequest['file']>> => {\n if (req.context) {\n delete req.context._payloadVerifiedProviderOriginal\n }\n\n if (\n !file ||\n typeof file !== 'object' ||\n typeof file.filename !== 'string' ||\n typeof file.mimeType !== 'string' ||\n !Number.isSafeInteger(file.size) ||\n file.size < 0 ||\n !file.uploadReference ||\n typeof file.uploadReference !== 'object' ||\n Array.isArray(file.uploadReference)\n ) {\n throw new APIError('Invalid upload reference.', 400)\n }\n\n /**\n * Handlers fetch files uploaded to a storage provider. An uploadId points to a temporary file\n * already stored by Payload, so no handler is needed.\n */\n if ('uploadId' in file.uploadReference) {\n return getStagedFile({ collectionSlug, req, uploadReference: file.uploadReference })\n }\n\n const uploadConfig = req.payload.collections[collectionSlug]!.config.upload\n let allowOverwrite = false\n let providerFilename = file.filename\n let verifiedOriginal:\n | {\n _objectKey?: string\n filename: string\n key: string\n prefix?: string\n signedReceipt: string\n }\n | undefined\n\n if (uploadConfig?.uploadInstructions?.requiresUploadReceipt) {\n const signedReceipt =\n 'signedReceipt' in file.uploadReference ? file.uploadReference.signedReceipt : undefined\n const receipt = verifyClientUploadReceipt({\n collectionSlug,\n filename: file.filename,\n req,\n signedReceipt,\n })\n allowOverwrite = receipt.allowOverwrite === true\n let originalFilename: string\n let storageFilePath: string\n\n try {\n originalFilename = getOriginalFilename({ filename: file.filename })\n storageFilePath = normalizeStorageKey({ key: receipt.storageFilePath })\n } catch {\n throw new APIError('Invalid upload reference.', 400)\n }\n\n const expectedKeySuffix = [receipt.filePrefix, receipt._objectKey, originalFilename]\n .filter(Boolean)\n .join('/')\n\n if (\n (!allowOverwrite &&\n (!receipt._objectKey ||\n path.posix.basename(storageFilePath) !== originalFilename ||\n !`/${storageFilePath}`.endsWith(`/${expectedKeySuffix}`))) ||\n ('prefix' in file.uploadReference && file.uploadReference.prefix !== receipt.filePrefix) ||\n ('_objectKey' in file.uploadReference &&\n file.uploadReference._objectKey !== receipt._objectKey)\n ) {\n throw new APIError('Invalid upload reference.', 400)\n }\n\n providerFilename = allowOverwrite ? file.filename : originalFilename\n if (!allowOverwrite) {\n verifiedOriginal = {\n _objectKey: receipt._objectKey,\n filename: originalFilename,\n key: storageFilePath,\n prefix: receipt.filePrefix,\n signedReceipt: signedReceipt as string,\n }\n }\n file = {\n ...file,\n uploadReference: {\n _objectKey: receipt._objectKey,\n prefix: receipt.filePrefix,\n signedReceipt,\n },\n }\n }\n\n const prefix =\n 'prefix' in file.uploadReference && typeof file.uploadReference.prefix === 'string'\n ? file.uploadReference.prefix\n : undefined\n\n let sanitizedFilename: string\n\n try {\n sanitizedFilename = sanitizeFilename(file.filename)\n } catch {\n throw new APIError('Invalid upload reference.', 400)\n }\n\n if (\n sanitizedFilename !== file.filename ||\n (typeof prefix === 'string' && sanitizeUploadPrefix(prefix) !== prefix)\n ) {\n throw new APIError('Invalid upload reference.', 400)\n }\n\n // Provider-backed references are submitted with the document request. They may only claim a\n // new object identity; existing top-level and generated filenames already belong to another\n // document and must continue through that document's read access checks.\n if (\n !allowOverwrite &&\n (await docWithFilenameExists({\n collectionSlug,\n filename: file.filename,\n matchAnyPrefix: true,\n path: '',\n prefix,\n req,\n }))\n ) {\n throw new APIError('Invalid upload reference.', 400)\n }\n\n if (!uploadConfig || !uploadConfig.handlers) {\n throw new APIError('uploadConfig.handlers is not present for ' + collectionSlug)\n }\n\n const contentRequirement = getFileContentRequirement({\n hasSizeEdits: requestHasSizeEdits(req),\n hasTransformFileStages: await hasTransformFileStages({\n collectionSlug,\n mimeType: file.mimeType,\n req,\n }),\n mimeType: file.mimeType,\n uploadConfig,\n })\n const providerFile = { ...file, filename: providerFilename }\n const rememberVerifiedOriginal = () => {\n if (verifiedOriginal) {\n req.context ??= {}\n req.context._payloadVerifiedProviderOriginal = verifiedOriginal\n }\n }\n\n // Nothing downstream reads this file's content - use the client-reported metadata directly\n // instead of re-downloading a file that, for a chunked upload, can be far larger than\n // the server's available memory or disk.\n if (contentRequirement === 'none') {\n const response = await fetchUploadResponse({\n collectionSlug,\n file: providerFile,\n rangeHeader: file.size > 0 ? 'bytes=0-0' : undefined,\n req,\n uploadConfig,\n })\n assertProviderFileSize({ expectedSize: file.size, response })\n const prefix = await readBoundedPrefix(response, 1)\n if (file.size > 0 && prefix.length === 0) {\n throw new APIError('Uploaded source is not readable.', 400)\n }\n rememberVerifiedOriginal()\n\n return {\n name: file.filename,\n data: Buffer.alloc(0),\n mimetype: file.mimeType,\n size: file.size,\n uploadReference: file.uploadReference,\n }\n }\n\n if (contentRequirement === 'header') {\n const headerFile = await fetchHeaderOnly({\n collectionSlug,\n file: providerFile,\n req,\n uploadConfig,\n })\n if (headerFile) {\n rememberVerifiedOriginal()\n return { ...headerFile, name: file.filename }\n }\n // The header wasn't enough to determine the image's dimensions - fall through to a full fetch.\n }\n\n const response = await fetchUploadResponse({\n collectionSlug,\n file: providerFile,\n req,\n uploadConfig,\n })\n\n const tempFilePath = await streamResponseToTempFile({ req, response })\n if ((await stat(tempFilePath)).size !== file.size) {\n await rm(tempFilePath, { force: true })\n throw new APIError('Uploaded source size does not match the declared size.', 400)\n }\n req.context ??= {}\n req.context._payloadClientUploadTempFile = true\n rememberVerifiedOriginal()\n\n return {\n name: file.filename,\n data: Buffer.alloc(0),\n mimetype: response.headers.get('Content-Type') || file.mimeType,\n size: file.size,\n tempFilePath,\n uploadReference: file.uploadReference,\n }\n}\n\n/**\n * Whether the request's `uploadEdits` query param carries a crop or resize edit - reads the raw\n * `req.query.uploadEdits`, since a full parse (with its `data`/`originalDoc` focal-point\n * fallback, done in generateFileData.ts) isn't available yet at this point in the request\n * lifecycle.\n */\nconst requestHasSizeEdits = (req: PayloadRequest): boolean => {\n const uploadEdits = req.query?.uploadEdits\n if (typeof uploadEdits !== 'object' || uploadEdits === null) {\n return false\n }\n\n return hasCropOrResizeEdit(uploadEdits as UploadEdits)\n}\n\n/**\n * Whether a transformer that reads the whole file will run `transformFile` on this upload. A\n * bridge transformer (e.g. `sharpTransformer`) is excluded: it projects what it needs onto the\n * sanitized upload config (`hasImageAdjustments`, `variants`) at startup instead.\n */\nconst hasTransformFileStages = async ({\n collectionSlug,\n mimeType,\n req,\n}: {\n collectionSlug: string\n mimeType: string\n req: PayloadRequest\n}): Promise<boolean> => {\n const transformers = req.payload.config.upload?.transformers ?? []\n\n if (transformers.length === 0) {\n return false\n }\n\n const pipeline = await planTransformerPipeline({\n args: { collectionSlug, mimeType, operation: 'upload', req },\n capability: 'transformFile',\n transformers,\n })\n\n return pipeline.some((transformer) => !getUploadTransformerInternal(transformer)?.prepareUpload)\n}\n\n/**\n * Fetches only the first `HEADER_PROBE_BYTE_LENGTH` bytes of the upload (via a best-effort byte\n * range request) and uses them to probe an image's dimensions, without downloading the rest of\n * the file. Returns null if that isn't enough to determine the dimensions, so the caller can\n * fall back to a full fetch.\n */\nconst fetchHeaderOnly = async ({\n collectionSlug,\n file,\n req,\n uploadConfig,\n}: {\n collectionSlug: string\n file: UploadInstructions['file']\n req: PayloadRequest\n uploadConfig: SanitizedUploadConfig\n}): Promise<NonNullable<PayloadRequest['file']> | null> => {\n const response = await fetchUploadResponse({\n collectionSlug,\n file,\n rangeHeader: `bytes=0-${HEADER_PROBE_BYTE_LENGTH - 1}`,\n req,\n uploadConfig,\n })\n assertProviderFileSize({ expectedSize: file.size, response })\n\n const headerBuffer = await readBoundedPrefix(response, HEADER_PROBE_BYTE_LENGTH)\n\n try {\n await getImageSize({\n file: {\n name: file.filename,\n data: headerBuffer,\n mimetype: file.mimeType,\n size: file.size,\n },\n })\n } catch {\n return null\n }\n\n return {\n name: file.filename,\n data: headerBuffer,\n mimetype: response.headers.get('Content-Type') || file.mimeType,\n size: file.size,\n uploadReference: file.uploadReference,\n }\n}\n\nconst assertProviderFileSize = ({\n expectedSize,\n response,\n}: {\n expectedSize: number\n response: Response\n}): void => {\n const contentRange = response.headers.get('Content-Range')\n const reportedSize =\n response.status === 206\n ? contentRange?.match(/^bytes \\d+-\\d+\\/(\\d+)$/)?.[1]\n : response.headers.get('Content-Length')\n\n if (\n reportedSize === undefined ||\n reportedSize === null ||\n Number(reportedSize) !== expectedSize\n ) {\n throw new APIError('Uploaded source size does not match the declared size.', 400)\n }\n}\n\n/**\n * Runs the collection's upload handlers, following a single redirect if one is returned.\n * `rangeHeader`, when passed, is a best-effort hint - handlers that ignore it simply return the\n * full file, which callers must still bound their own reads against.\n */\nconst fetchUploadResponse = async ({\n collectionSlug,\n file,\n rangeHeader,\n req,\n uploadConfig,\n}: {\n collectionSlug: string\n file: UploadInstructions['file']\n rangeHeader?: string\n req: PayloadRequest\n uploadConfig: SanitizedUploadConfig\n}): Promise<Response> => {\n const scopedReq = rangeHeader ? withRangeHeader(req, rangeHeader) : req\n\n let response: null | Response = null\n let error: unknown\n\n for (const handler of uploadConfig.handlers!) {\n try {\n const result = await handler(scopedReq, {\n doc: null!,\n params: {\n collection: collectionSlug,\n filename: file.filename,\n uploadReference: file.uploadReference,\n },\n })\n if (result) {\n response = result\n /**\n * - If a handler returns a Response, the response will be sent to the client and no further handlers will be run.\n * - If a handler returns null, the next handler will be run.\n *\n * @see packages/payload/src/uploads/types.ts\n */\n break\n }\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 response = await fetch(redirectUrl, {\n ...(rangeHeader && { headers: { Range: rangeHeader } }),\n })\n }\n }\n\n if (!response.ok) {\n throw new APIError('Uploaded source is not readable.', 400)\n }\n\n return response\n}\n\n/**\n * Overrides `req.headers` with a `Range` header while leaving the real incoming request\n * untouched. Uses a Proxy, rather than cloning `req`, because `req` is a native `Request`\n * instance at runtime - accessors like `.signal` are brand-checked against `req`'s internal\n * slots, so a plain clone (e.g. `Object.create(req)`) throws once a handler reads one of them.\n * Forwarding reads through `Reflect.get(req, prop, req)` keeps `this` bound to the real `req`\n * so those accessors keep working.\n */\nconst withRangeHeader = (req: PayloadRequest, rangeHeader: string): PayloadRequest => {\n const headers = new Headers(req.headers)\n headers.set('range', rangeHeader)\n\n return new Proxy(req, {\n get(target, prop) {\n return prop === 'headers' ? headers : Reflect.get(target, prop, target)\n },\n })\n}\n\n/**\n * Reads at most `maxBytes` from the response body, then cancels the reader - so a handler that\n * ignores the range hint and starts streaming the whole file is still cut short on our end.\n */\nconst readBoundedPrefix = async (response: Response, maxBytes: number): Promise<Buffer> => {\n if (!response.body) {\n return Buffer.alloc(0)\n }\n\n const reader = response.body.getReader()\n const chunks: Buffer[] = []\n let total = 0\n\n try {\n while (total < maxBytes) {\n const { done, value } = await reader.read()\n if (done || !value) {\n break\n }\n\n const remaining = maxBytes - total\n const chunk = value.byteLength > remaining ? value.subarray(0, remaining) : value\n chunks.push(Buffer.from(chunk))\n total += chunk.byteLength\n }\n } finally {\n await reader.cancel().catch(() => {})\n }\n\n return Buffer.concat(chunks, total)\n}\n\n/**\n * Streams the fetched upload straight to disk instead of buffering it in memory. A client upload\n * (e.g. Azure's chunkLargeFiles) can be far larger than the server's available memory, so this\n * avoids re-downloading the whole file into a single in-memory buffer just to read it back out.\n */\nconst streamResponseToTempFile = async ({\n req,\n response,\n}: {\n req: PayloadRequest\n response: Response\n}): Promise<string> => {\n if (!response.body) {\n throw new APIError('Expected a response body from the upload handler.')\n }\n\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 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","stat","os","path","Readable","pipeline","APIError","sanitizeFilename","sanitizeUploadPrefix","verifyClientUploadReceipt","docWithFilenameExists","getOriginalFilename","normalizeStorageKey","getFileContentRequirement","HEADER_PROBE_BYTE_LENGTH","getImageSize","hasCropOrResizeEdit","getStagedFile","planTransformerPipeline","getUploadTransformerInternal","getFileFromUploadInstructions","collectionSlug","file","req","context","_payloadVerifiedProviderOriginal","filename","mimeType","Number","isSafeInteger","size","uploadReference","Array","isArray","uploadConfig","payload","collections","config","upload","allowOverwrite","providerFilename","verifiedOriginal","uploadInstructions","requiresUploadReceipt","signedReceipt","undefined","receipt","originalFilename","storageFilePath","key","expectedKeySuffix","filePrefix","_objectKey","filter","Boolean","join","posix","basename","endsWith","prefix","sanitizedFilename","matchAnyPrefix","handlers","contentRequirement","hasSizeEdits","requestHasSizeEdits","hasTransformFileStages","providerFile","rememberVerifiedOriginal","response","fetchUploadResponse","rangeHeader","assertProviderFileSize","expectedSize","readBoundedPrefix","length","name","data","Buffer","alloc","mimetype","headerFile","fetchHeaderOnly","tempFilePath","streamResponseToTempFile","force","_payloadClientUploadTempFile","headers","get","uploadEdits","query","transformers","args","operation","capability","some","transformer","prepareUpload","headerBuffer","contentRange","reportedSize","status","match","scopedReq","withRangeHeader","error","handler","result","doc","params","collection","err","logger","redirectUrl","fetch","Range","ok","Headers","set","Proxy","target","prop","Reflect","maxBytes","body","reader","getReader","chunks","total","done","value","read","remaining","chunk","byteLength","subarray","push","from","cancel","catch","concat","tempFileDir","tmpdir","recursive","fromWeb","createWriteStream"],"mappings":"AAEA,SAASA,UAAU,QAAQ,cAAa;AACxC,OAAOC,QAAQ,UAAS;AACxB,SAASC,KAAK,EAAEC,EAAE,EAAEC,IAAI,QAAQ,mBAAkB;AAClD,OAAOC,QAAQ,UAAS;AACxB,OAAOC,UAAU,YAAW;AAC5B,SAASC,QAAQ,QAAQ,cAAa;AACtC,SAASC,QAAQ,QAAQ,uBAAsB;AAK/C,SAASC,QAAQ,QAAQ,wBAAuB;AAChD,SAASC,gBAAgB,QAAQ,mCAAkC;AACnE,SAASC,oBAAoB,QAAQ,uCAAsC;AAC3E,SAASC,yBAAyB,QAAQ,2BAA0B;AACpE,SAASC,qBAAqB,QAAQ,6BAA4B;AAClE,SAASC,mBAAmB,EAAEC,mBAAmB,QAAQ,6BAA4B;AACrF,SAASC,yBAAyB,EAAEC,wBAAwB,QAAQ,iCAAgC;AACpG,SAASC,YAAY,QAAQ,oBAAmB;AAChD,SAASC,mBAAmB,QAAQ,2BAA0B;AAC9D,SAASC,aAAa,QAAQ,oBAAmB;AACjD,SAASC,uBAAuB,QAAQ,4CAA2C;AACnF,SAASC,4BAA4B,QAAQ,4CAA2C;AAExF,OAAO,MAAMC,gCAAgC,OAAO,EAClDC,cAAc,EACdC,IAAI,EACJC,GAAG,EAKJ;IACC,IAAIA,IAAIC,OAAO,EAAE;QACf,OAAOD,IAAIC,OAAO,CAACC,gCAAgC;IACrD;IAEA,IACE,CAACH,QACD,OAAOA,SAAS,YAChB,OAAOA,KAAKI,QAAQ,KAAK,YACzB,OAAOJ,KAAKK,QAAQ,KAAK,YACzB,CAACC,OAAOC,aAAa,CAACP,KAAKQ,IAAI,KAC/BR,KAAKQ,IAAI,GAAG,KACZ,CAACR,KAAKS,eAAe,IACrB,OAAOT,KAAKS,eAAe,KAAK,YAChCC,MAAMC,OAAO,CAACX,KAAKS,eAAe,GAClC;QACA,MAAM,IAAIzB,SAAS,6BAA6B;IAClD;IAEA;;;GAGC,GACD,IAAI,cAAcgB,KAAKS,eAAe,EAAE;QACtC,OAAOd,cAAc;YAAEI;YAAgBE;YAAKQ,iBAAiBT,KAAKS,eAAe;QAAC;IACpF;IAEA,MAAMG,eAAeX,IAAIY,OAAO,CAACC,WAAW,CAACf,eAAe,CAAEgB,MAAM,CAACC,MAAM;IAC3E,IAAIC,iBAAiB;IACrB,IAAIC,mBAAmBlB,KAAKI,QAAQ;IACpC,IAAIe;IAUJ,IAAIP,cAAcQ,oBAAoBC,uBAAuB;QAC3D,MAAMC,gBACJ,mBAAmBtB,KAAKS,eAAe,GAAGT,KAAKS,eAAe,CAACa,aAAa,GAAGC;QACjF,MAAMC,UAAUrC,0BAA0B;YACxCY;YACAK,UAAUJ,KAAKI,QAAQ;YACvBH;YACAqB;QACF;QACAL,iBAAiBO,QAAQP,cAAc,KAAK;QAC5C,IAAIQ;QACJ,IAAIC;QAEJ,IAAI;YACFD,mBAAmBpC,oBAAoB;gBAAEe,UAAUJ,KAAKI,QAAQ;YAAC;YACjEsB,kBAAkBpC,oBAAoB;gBAAEqC,KAAKH,QAAQE,eAAe;YAAC;QACvE,EAAE,OAAM;YACN,MAAM,IAAI1C,SAAS,6BAA6B;QAClD;QAEA,MAAM4C,oBAAoB;YAACJ,QAAQK,UAAU;YAAEL,QAAQM,UAAU;YAAEL;SAAiB,CACjFM,MAAM,CAACC,SACPC,IAAI,CAAC;QAER,IACE,AAAC,CAAChB,kBACC,CAAA,CAACO,QAAQM,UAAU,IAClBjD,KAAKqD,KAAK,CAACC,QAAQ,CAACT,qBAAqBD,oBACzC,CAAC,CAAC,CAAC,EAAEC,iBAAiB,CAACU,QAAQ,CAAC,CAAC,CAAC,EAAER,mBAAmB,CAAA,KAC1D,YAAY5B,KAAKS,eAAe,IAAIT,KAAKS,eAAe,CAAC4B,MAAM,KAAKb,QAAQK,UAAU,IACtF,gBAAgB7B,KAAKS,eAAe,IACnCT,KAAKS,eAAe,CAACqB,UAAU,KAAKN,QAAQM,UAAU,EACxD;YACA,MAAM,IAAI9C,SAAS,6BAA6B;QAClD;QAEAkC,mBAAmBD,iBAAiBjB,KAAKI,QAAQ,GAAGqB;QACpD,IAAI,CAACR,gBAAgB;YACnBE,mBAAmB;gBACjBW,YAAYN,QAAQM,UAAU;gBAC9B1B,UAAUqB;gBACVE,KAAKD;gBACLW,QAAQb,QAAQK,UAAU;gBAC1BP,eAAeA;YACjB;QACF;QACAtB,OAAO;YACL,GAAGA,IAAI;YACPS,iBAAiB;gBACfqB,YAAYN,QAAQM,UAAU;gBAC9BO,QAAQb,QAAQK,UAAU;gBAC1BP;YACF;QACF;IACF;IAEA,MAAMe,SACJ,YAAYrC,KAAKS,eAAe,IAAI,OAAOT,KAAKS,eAAe,CAAC4B,MAAM,KAAK,WACvErC,KAAKS,eAAe,CAAC4B,MAAM,GAC3Bd;IAEN,IAAIe;IAEJ,IAAI;QACFA,oBAAoBrD,iBAAiBe,KAAKI,QAAQ;IACpD,EAAE,OAAM;QACN,MAAM,IAAIpB,SAAS,6BAA6B;IAClD;IAEA,IACEsD,sBAAsBtC,KAAKI,QAAQ,IAClC,OAAOiC,WAAW,YAAYnD,qBAAqBmD,YAAYA,QAChE;QACA,MAAM,IAAIrD,SAAS,6BAA6B;IAClD;IAEA,4FAA4F;IAC5F,4FAA4F;IAC5F,yEAAyE;IACzE,IACE,CAACiC,kBACA,MAAM7B,sBAAsB;QAC3BW;QACAK,UAAUJ,KAAKI,QAAQ;QACvBmC,gBAAgB;QAChB1D,MAAM;QACNwD;QACApC;IACF,IACA;QACA,MAAM,IAAIjB,SAAS,6BAA6B;IAClD;IAEA,IAAI,CAAC4B,gBAAgB,CAACA,aAAa4B,QAAQ,EAAE;QAC3C,MAAM,IAAIxD,SAAS,8CAA8Ce;IACnE;IAEA,MAAM0C,qBAAqBlD,0BAA0B;QACnDmD,cAAcC,oBAAoB1C;QAClC2C,wBAAwB,MAAMA,uBAAuB;YACnD7C;YACAM,UAAUL,KAAKK,QAAQ;YACvBJ;QACF;QACAI,UAAUL,KAAKK,QAAQ;QACvBO;IACF;IACA,MAAMiC,eAAe;QAAE,GAAG7C,IAAI;QAAEI,UAAUc;IAAiB;IAC3D,MAAM4B,2BAA2B;QAC/B,IAAI3B,kBAAkB;YACpBlB,IAAIC,OAAO,KAAK,CAAC;YACjBD,IAAIC,OAAO,CAACC,gCAAgC,GAAGgB;QACjD;IACF;IAEA,2FAA2F;IAC3F,sFAAsF;IACtF,yCAAyC;IACzC,IAAIsB,uBAAuB,QAAQ;QACjC,MAAMM,WAAW,MAAMC,oBAAoB;YACzCjD;YACAC,MAAM6C;YACNI,aAAajD,KAAKQ,IAAI,GAAG,IAAI,cAAce;YAC3CtB;YACAW;QACF;QACAsC,uBAAuB;YAAEC,cAAcnD,KAAKQ,IAAI;YAAEuC;QAAS;QAC3D,MAAMV,SAAS,MAAMe,kBAAkBL,UAAU;QACjD,IAAI/C,KAAKQ,IAAI,GAAG,KAAK6B,OAAOgB,MAAM,KAAK,GAAG;YACxC,MAAM,IAAIrE,SAAS,oCAAoC;QACzD;QACA8D;QAEA,OAAO;YACLQ,MAAMtD,KAAKI,QAAQ;YACnBmD,MAAMC,OAAOC,KAAK,CAAC;YACnBC,UAAU1D,KAAKK,QAAQ;YACvBG,MAAMR,KAAKQ,IAAI;YACfC,iBAAiBT,KAAKS,eAAe;QACvC;IACF;IAEA,IAAIgC,uBAAuB,UAAU;QACnC,MAAMkB,aAAa,MAAMC,gBAAgB;YACvC7D;YACAC,MAAM6C;YACN5C;YACAW;QACF;QACA,IAAI+C,YAAY;YACdb;YACA,OAAO;gBAAE,GAAGa,UAAU;gBAAEL,MAAMtD,KAAKI,QAAQ;YAAC;QAC9C;IACA,+FAA+F;IACjG;IAEA,MAAM2C,WAAW,MAAMC,oBAAoB;QACzCjD;QACAC,MAAM6C;QACN5C;QACAW;IACF;IAEA,MAAMiD,eAAe,MAAMC,yBAAyB;QAAE7D;QAAK8C;IAAS;IACpE,IAAI,AAAC,CAAA,MAAMpE,KAAKkF,aAAY,EAAGrD,IAAI,KAAKR,KAAKQ,IAAI,EAAE;QACjD,MAAM9B,GAAGmF,cAAc;YAAEE,OAAO;QAAK;QACrC,MAAM,IAAI/E,SAAS,0DAA0D;IAC/E;IACAiB,IAAIC,OAAO,KAAK,CAAC;IACjBD,IAAIC,OAAO,CAAC8D,4BAA4B,GAAG;IAC3ClB;IAEA,OAAO;QACLQ,MAAMtD,KAAKI,QAAQ;QACnBmD,MAAMC,OAAOC,KAAK,CAAC;QACnBC,UAAUX,SAASkB,OAAO,CAACC,GAAG,CAAC,mBAAmBlE,KAAKK,QAAQ;QAC/DG,MAAMR,KAAKQ,IAAI;QACfqD;QACApD,iBAAiBT,KAAKS,eAAe;IACvC;AACF,EAAC;AAED;;;;;CAKC,GACD,MAAMkC,sBAAsB,CAAC1C;IAC3B,MAAMkE,cAAclE,IAAImE,KAAK,EAAED;IAC/B,IAAI,OAAOA,gBAAgB,YAAYA,gBAAgB,MAAM;QAC3D,OAAO;IACT;IAEA,OAAOzE,oBAAoByE;AAC7B;AAEA;;;;CAIC,GACD,MAAMvB,yBAAyB,OAAO,EACpC7C,cAAc,EACdM,QAAQ,EACRJ,GAAG,EAKJ;IACC,MAAMoE,eAAepE,IAAIY,OAAO,CAACE,MAAM,CAACC,MAAM,EAAEqD,gBAAgB,EAAE;IAElE,IAAIA,aAAahB,MAAM,KAAK,GAAG;QAC7B,OAAO;IACT;IAEA,MAAMtE,WAAW,MAAMa,wBAAwB;QAC7C0E,MAAM;YAAEvE;YAAgBM;YAAUkE,WAAW;YAAUtE;QAAI;QAC3DuE,YAAY;QACZH;IACF;IAEA,OAAOtF,SAAS0F,IAAI,CAAC,CAACC,cAAgB,CAAC7E,6BAA6B6E,cAAcC;AACpF;AAEA;;;;;CAKC,GACD,MAAMf,kBAAkB,OAAO,EAC7B7D,cAAc,EACdC,IAAI,EACJC,GAAG,EACHW,YAAY,EAMb;IACC,MAAMmC,WAAW,MAAMC,oBAAoB;QACzCjD;QACAC;QACAiD,aAAa,CAAC,QAAQ,EAAEzD,2BAA2B,GAAG;QACtDS;QACAW;IACF;IACAsC,uBAAuB;QAAEC,cAAcnD,KAAKQ,IAAI;QAAEuC;IAAS;IAE3D,MAAM6B,eAAe,MAAMxB,kBAAkBL,UAAUvD;IAEvD,IAAI;QACF,MAAMC,aAAa;YACjBO,MAAM;gBACJsD,MAAMtD,KAAKI,QAAQ;gBACnBmD,MAAMqB;gBACNlB,UAAU1D,KAAKK,QAAQ;gBACvBG,MAAMR,KAAKQ,IAAI;YACjB;QACF;IACF,EAAE,OAAM;QACN,OAAO;IACT;IAEA,OAAO;QACL8C,MAAMtD,KAAKI,QAAQ;QACnBmD,MAAMqB;QACNlB,UAAUX,SAASkB,OAAO,CAACC,GAAG,CAAC,mBAAmBlE,KAAKK,QAAQ;QAC/DG,MAAMR,KAAKQ,IAAI;QACfC,iBAAiBT,KAAKS,eAAe;IACvC;AACF;AAEA,MAAMyC,yBAAyB,CAAC,EAC9BC,YAAY,EACZJ,QAAQ,EAIT;IACC,MAAM8B,eAAe9B,SAASkB,OAAO,CAACC,GAAG,CAAC;IAC1C,MAAMY,eACJ/B,SAASgC,MAAM,KAAK,MAChBF,cAAcG,MAAM,2BAA2B,CAAC,EAAE,GAClDjC,SAASkB,OAAO,CAACC,GAAG,CAAC;IAE3B,IACEY,iBAAiBvD,aACjBuD,iBAAiB,QACjBxE,OAAOwE,kBAAkB3B,cACzB;QACA,MAAM,IAAInE,SAAS,0DAA0D;IAC/E;AACF;AAEA;;;;CAIC,GACD,MAAMgE,sBAAsB,OAAO,EACjCjD,cAAc,EACdC,IAAI,EACJiD,WAAW,EACXhD,GAAG,EACHW,YAAY,EAOb;IACC,MAAMqE,YAAYhC,cAAciC,gBAAgBjF,KAAKgD,eAAehD;IAEpE,IAAI8C,WAA4B;IAChC,IAAIoC;IAEJ,KAAK,MAAMC,WAAWxE,aAAa4B,QAAQ,CAAG;QAC5C,IAAI;YACF,MAAM6C,SAAS,MAAMD,QAAQH,WAAW;gBACtCK,KAAK;gBACLC,QAAQ;oBACNC,YAAYzF;oBACZK,UAAUJ,KAAKI,QAAQ;oBACvBK,iBAAiBT,KAAKS,eAAe;gBACvC;YACF;YACA,IAAI4E,QAAQ;gBACVtC,WAAWsC;gBAOX;YACF;QACF,EAAE,OAAOI,KAAK;YACZN,QAAQM;QACV;IACF;IAEA,IAAI,CAAC1C,UAAU;QACb,IAAIoC,OAAO;YACTlF,IAAIY,OAAO,CAAC6E,MAAM,CAACP,KAAK,CAACA;QAC3B;QAEA,MAAM,IAAInG,SAAS;IACrB;IAEA,IAAI+D,SAASgC,MAAM,IAAI,OAAOhC,SAASgC,MAAM,GAAG,KAAK;QACnD,MAAMY,cAAc5C,SAASkB,OAAO,CAACC,GAAG,CAAC;QACzC,IAAIyB,aAAa;YACf5C,WAAW,MAAM6C,MAAMD,aAAa;gBAClC,GAAI1C,eAAe;oBAAEgB,SAAS;wBAAE4B,OAAO5C;oBAAY;gBAAE,CAAC;YACxD;QACF;IACF;IAEA,IAAI,CAACF,SAAS+C,EAAE,EAAE;QAChB,MAAM,IAAI9G,SAAS,oCAAoC;IACzD;IAEA,OAAO+D;AACT;AAEA;;;;;;;CAOC,GACD,MAAMmC,kBAAkB,CAACjF,KAAqBgD;IAC5C,MAAMgB,UAAU,IAAI8B,QAAQ9F,IAAIgE,OAAO;IACvCA,QAAQ+B,GAAG,CAAC,SAAS/C;IAErB,OAAO,IAAIgD,MAAMhG,KAAK;QACpBiE,KAAIgC,MAAM,EAAEC,IAAI;YACd,OAAOA,SAAS,YAAYlC,UAAUmC,QAAQlC,GAAG,CAACgC,QAAQC,MAAMD;QAClE;IACF;AACF;AAEA;;;CAGC,GACD,MAAM9C,oBAAoB,OAAOL,UAAoBsD;IACnD,IAAI,CAACtD,SAASuD,IAAI,EAAE;QAClB,OAAO9C,OAAOC,KAAK,CAAC;IACtB;IAEA,MAAM8C,SAASxD,SAASuD,IAAI,CAACE,SAAS;IACtC,MAAMC,SAAmB,EAAE;IAC3B,IAAIC,QAAQ;IAEZ,IAAI;QACF,MAAOA,QAAQL,SAAU;YACvB,MAAM,EAAEM,IAAI,EAAEC,KAAK,EAAE,GAAG,MAAML,OAAOM,IAAI;YACzC,IAAIF,QAAQ,CAACC,OAAO;gBAClB;YACF;YAEA,MAAME,YAAYT,WAAWK;YAC7B,MAAMK,QAAQH,MAAMI,UAAU,GAAGF,YAAYF,MAAMK,QAAQ,CAAC,GAAGH,aAAaF;YAC5EH,OAAOS,IAAI,CAAC1D,OAAO2D,IAAI,CAACJ;YACxBL,SAASK,MAAMC,UAAU;QAC3B;IACF,SAAU;QACR,MAAMT,OAAOa,MAAM,GAAGC,KAAK,CAAC,KAAO;IACrC;IAEA,OAAO7D,OAAO8D,MAAM,CAACb,QAAQC;AAC/B;AAEA;;;;CAIC,GACD,MAAM5C,2BAA2B,OAAO,EACtC7D,GAAG,EACH8C,QAAQ,EAIT;IACC,IAAI,CAACA,SAASuD,IAAI,EAAE;QAClB,MAAM,IAAItH,SAAS;IACrB;IAEA,MAAMuI,cAActH,IAAIY,OAAO,CAACE,MAAM,CAACC,MAAM,EAAEuG,eAAe3I,GAAG4I,MAAM;IACvE,MAAM/I,MAAM8I,aAAa;QAAEE,WAAW;IAAK;IAC3C,MAAM5D,eAAehF,KAAKoD,IAAI,CAACsF,aAAa,CAAC,sBAAsB,EAAEhJ,cAAc;IAEnF,IAAI;QACF,MAAMQ,SACJD,SAAS4I,OAAO,CAAC3E,SAASuD,IAAI,GAC9B9H,GAAGmJ,iBAAiB,CAAC9D;IAEzB,EAAE,OAAOsB,OAAO;QACd,MAAMzG,GAAGmF,cAAc;YAAEE,OAAO;QAAK;QACrC,MAAMoB;IACR;IAEA,OAAOtB;AACT"}
1
+ {"version":3,"sources":["../../src/uploads/getFileFromUploadInstructions.ts"],"sourcesContent":["import type { ReadableStream } from 'node:stream/web'\n\nimport { randomUUID } from 'node:crypto'\nimport fs from 'node:fs'\nimport { mkdir, rm, stat } from 'node:fs/promises'\nimport os from 'node:os'\nimport path from 'node:path'\nimport { Readable } from 'node:stream'\nimport { pipeline } from 'node:stream/promises'\n\nimport type { PayloadRequest } from '../types/index.js'\nimport type { SanitizedUploadConfig, UploadEdits, UploadInstructions } from './types.js'\n\nimport { APIError } from '../errors/APIError.js'\nimport { sanitizeFilename } from '../utilities/sanitizeFilename.js'\nimport { sanitizeUploadPrefix } from '../utilities/sanitizeUploadPrefix.js'\nimport { verifyClientUploadReceipt } from './clientUploadReceipt.js'\nimport { docWithFilenameExists } from './docWithFilenameExists.js'\nimport { getOriginalFilename, normalizeStorageKey } from './fileVersioning/naming.js'\nimport { getFileContentRequirement, HEADER_PROBE_BYTE_LENGTH } from './getFileContentRequirement.js'\nimport { getImageSize } from './getImageSize.js'\nimport { hasCropOrResizeEdit } from './hasCropOrResizeEdit.js'\nimport { getStagedFile } from './stagedUpload.js'\nimport { planTransformerPipeline } from './transformers/planTransformerPipeline.js'\nimport { getUploadTransformerInternal } from './transformers/uploadTransformerBridge.js'\n\nexport const getFileFromUploadInstructions = async ({\n collectionSlug,\n file,\n req,\n}: {\n collectionSlug: string\n file: UploadInstructions['file']\n req: PayloadRequest\n}): Promise<NonNullable<PayloadRequest['file']>> => {\n if (req.context) {\n delete req.context._payloadVerifiedProviderOriginal\n }\n\n if (\n !file ||\n typeof file !== 'object' ||\n typeof file.filename !== 'string' ||\n typeof file.mimeType !== 'string' ||\n !Number.isSafeInteger(file.size) ||\n file.size < 0 ||\n !file.uploadReference ||\n typeof file.uploadReference !== 'object' ||\n Array.isArray(file.uploadReference)\n ) {\n throw new APIError('Invalid upload reference.', 400)\n }\n\n /**\n * Handlers fetch files uploaded to a storage provider. An uploadId points to a temporary file\n * already stored by Payload, so no handler is needed.\n */\n if ('uploadId' in file.uploadReference) {\n return getStagedFile({ collectionSlug, req, uploadReference: file.uploadReference })\n }\n\n const uploadConfig = req.payload.collections[collectionSlug]!.config.upload\n let allowOverwrite = false\n let providerFilename = file.filename\n let verifiedOriginal:\n | {\n _objectKey?: string\n filename: string\n key: string\n prefix?: string\n signedReceipt: string\n }\n | undefined\n\n if (uploadConfig?.uploadInstructions?.requiresUploadReceipt) {\n const signedReceipt =\n 'signedReceipt' in file.uploadReference ? file.uploadReference.signedReceipt : undefined\n const receipt = verifyClientUploadReceipt({\n collectionSlug,\n filename: file.filename,\n req,\n signedReceipt,\n })\n allowOverwrite = receipt.allowOverwrite === true\n let originalFilename: string\n let storageFilePath: string\n\n try {\n originalFilename = getOriginalFilename({ filename: file.filename })\n storageFilePath = normalizeStorageKey({ key: receipt.storageFilePath })\n } catch {\n throw new APIError('Invalid upload reference.', 400)\n }\n\n const expectedKeySuffix = [receipt.filePrefix, receipt._objectKey, originalFilename]\n .filter(Boolean)\n .join('/')\n\n if (\n (!allowOverwrite &&\n (!receipt._objectKey ||\n path.posix.basename(storageFilePath) !== originalFilename ||\n !`/${storageFilePath}`.endsWith(`/${expectedKeySuffix}`))) ||\n ('prefix' in file.uploadReference && file.uploadReference.prefix !== receipt.filePrefix) ||\n ('_objectKey' in file.uploadReference &&\n file.uploadReference._objectKey !== receipt._objectKey)\n ) {\n throw new APIError('Invalid upload reference.', 400)\n }\n\n providerFilename = allowOverwrite ? file.filename : originalFilename\n if (!allowOverwrite) {\n verifiedOriginal = {\n _objectKey: receipt._objectKey,\n filename: originalFilename,\n key: storageFilePath,\n prefix: receipt.filePrefix,\n signedReceipt: signedReceipt as string,\n }\n }\n file = {\n ...file,\n uploadReference: {\n _objectKey: receipt._objectKey,\n prefix: receipt.filePrefix,\n signedReceipt,\n },\n }\n }\n\n const prefix =\n 'prefix' in file.uploadReference && typeof file.uploadReference.prefix === 'string'\n ? file.uploadReference.prefix\n : undefined\n\n let sanitizedFilename: string\n\n try {\n sanitizedFilename = sanitizeFilename(file.filename)\n } catch {\n throw new APIError('Invalid upload reference.', 400)\n }\n\n if (\n sanitizedFilename !== file.filename ||\n (typeof prefix === 'string' && sanitizeUploadPrefix(prefix) !== prefix)\n ) {\n throw new APIError('Invalid upload reference.', 400)\n }\n\n // Provider-backed references are submitted with the document request. They may only claim a\n // new object identity; existing top-level and generated filenames already belong to another\n // document and must continue through that document's read access checks.\n if (\n !allowOverwrite &&\n (await docWithFilenameExists({\n collectionSlug,\n filename: file.filename,\n matchAnyPrefix: true,\n path: '',\n prefix,\n req,\n }))\n ) {\n throw new APIError('Invalid upload reference.', 400)\n }\n\n if (!uploadConfig || !uploadConfig.handlers) {\n throw new APIError('uploadConfig.handlers is not present for ' + collectionSlug)\n }\n\n const contentRequirement = getFileContentRequirement({\n hasSizeEdits: requestHasSizeEdits(req),\n hasTransformFileStages: await hasTransformFileStages({\n collectionSlug,\n mimeType: file.mimeType,\n req,\n }),\n mimeType: file.mimeType,\n uploadConfig,\n })\n const providerFile = { ...file, filename: providerFilename }\n const rememberVerifiedOriginal = () => {\n if (verifiedOriginal) {\n req.context ??= {}\n req.context._payloadVerifiedProviderOriginal = verifiedOriginal\n }\n }\n\n // Nothing downstream reads this file's content - use the client-reported metadata directly\n // instead of re-downloading a file that, for a chunked upload, can be far larger than\n // the server's available memory or disk.\n if (contentRequirement === 'none') {\n const response = await fetchUploadResponse({\n collectionSlug,\n file: providerFile,\n rangeHeader: file.size > 0 ? 'bytes=0-0' : undefined,\n req,\n uploadConfig,\n })\n assertProviderFileSize({ expectedSize: file.size, response })\n const prefix = await readBoundedPrefix(response, 1)\n if (file.size > 0 && prefix.length === 0) {\n throw new APIError('Uploaded source is not readable.', 400)\n }\n rememberVerifiedOriginal()\n\n return {\n name: file.filename,\n data: Buffer.alloc(0),\n mimetype: file.mimeType,\n size: file.size,\n uploadReference: file.uploadReference,\n }\n }\n\n if (contentRequirement === 'header') {\n const headerFile = await fetchHeaderOnly({\n collectionSlug,\n file: providerFile,\n req,\n uploadConfig,\n })\n if (headerFile) {\n rememberVerifiedOriginal()\n return { ...headerFile, name: file.filename }\n }\n // The header wasn't enough to determine the image's dimensions - fall through to a full fetch.\n }\n\n const response = await fetchUploadResponse({\n collectionSlug,\n file: providerFile,\n req,\n uploadConfig,\n })\n\n const tempFilePath = await streamResponseToTempFile({ req, response })\n if ((await stat(tempFilePath)).size !== file.size) {\n await rm(tempFilePath, { force: true })\n throw new APIError('Uploaded source size does not match the declared size.', 400)\n }\n req.context ??= {}\n req.context._payloadClientUploadTempFile = true\n rememberVerifiedOriginal()\n\n return {\n name: file.filename,\n data: Buffer.alloc(0),\n mimetype: response.headers.get('Content-Type') || file.mimeType,\n size: file.size,\n tempFilePath,\n uploadReference: file.uploadReference,\n }\n}\n\n/**\n * Whether the request's `uploadEdits` query param carries a crop or resize edit - reads the raw\n * `req.query.uploadEdits`, since a full parse (with its `data`/`originalDoc` focal-point\n * fallback, done in generateFileData.ts) isn't available yet at this point in the request\n * lifecycle.\n */\nconst requestHasSizeEdits = (req: PayloadRequest): boolean => {\n const uploadEdits = req.query?.uploadEdits\n if (typeof uploadEdits !== 'object' || uploadEdits === null) {\n return false\n }\n\n return hasCropOrResizeEdit(uploadEdits)\n}\n\n/**\n * Whether a transformer that reads the whole file will run `transformFile` on this upload. A\n * bridge transformer (e.g. `sharpTransformer`) is excluded: it projects what it needs onto the\n * sanitized upload config (`hasImageAdjustments`, `variants`) at startup instead.\n */\nconst hasTransformFileStages = async ({\n collectionSlug,\n mimeType,\n req,\n}: {\n collectionSlug: string\n mimeType: string\n req: PayloadRequest\n}): Promise<boolean> => {\n const transformers = req.payload.config.upload?.transformers ?? []\n\n if (transformers.length === 0) {\n return false\n }\n\n const pipeline = await planTransformerPipeline({\n args: { collectionSlug, mimeType, operation: 'upload', req },\n capability: 'transformFile',\n transformers,\n })\n\n return pipeline.some((transformer) => !getUploadTransformerInternal(transformer)?.prepareUpload)\n}\n\n/**\n * Fetches only the first `HEADER_PROBE_BYTE_LENGTH` bytes of the upload (via a best-effort byte\n * range request) and uses them to probe an image's dimensions, without downloading the rest of\n * the file. Returns null if that isn't enough to determine the dimensions, so the caller can\n * fall back to a full fetch.\n */\nconst fetchHeaderOnly = async ({\n collectionSlug,\n file,\n req,\n uploadConfig,\n}: {\n collectionSlug: string\n file: UploadInstructions['file']\n req: PayloadRequest\n uploadConfig: SanitizedUploadConfig\n}): Promise<NonNullable<PayloadRequest['file']> | null> => {\n const response = await fetchUploadResponse({\n collectionSlug,\n file,\n rangeHeader: `bytes=0-${HEADER_PROBE_BYTE_LENGTH - 1}`,\n req,\n uploadConfig,\n })\n assertProviderFileSize({ expectedSize: file.size, response })\n\n const headerBuffer = await readBoundedPrefix(response, HEADER_PROBE_BYTE_LENGTH)\n\n try {\n await getImageSize({\n file: {\n name: file.filename,\n data: headerBuffer,\n mimetype: file.mimeType,\n size: file.size,\n },\n })\n } catch {\n return null\n }\n\n return {\n name: file.filename,\n data: headerBuffer,\n mimetype: response.headers.get('Content-Type') || file.mimeType,\n size: file.size,\n uploadReference: file.uploadReference,\n }\n}\n\nconst assertProviderFileSize = ({\n expectedSize,\n response,\n}: {\n expectedSize: number\n response: Response\n}): void => {\n const contentRange = response.headers.get('Content-Range')\n const reportedSize =\n response.status === 206\n ? contentRange?.match(/^bytes \\d+-\\d+\\/(\\d+)$/)?.[1]\n : response.headers.get('Content-Length')\n\n if (\n reportedSize === undefined ||\n reportedSize === null ||\n Number(reportedSize) !== expectedSize\n ) {\n throw new APIError('Uploaded source size does not match the declared size.', 400)\n }\n}\n\n/**\n * Runs the collection's upload handlers, following a single redirect if one is returned.\n * `rangeHeader`, when passed, is a best-effort hint - handlers that ignore it simply return the\n * full file, which callers must still bound their own reads against.\n */\nconst fetchUploadResponse = async ({\n collectionSlug,\n file,\n rangeHeader,\n req,\n uploadConfig,\n}: {\n collectionSlug: string\n file: UploadInstructions['file']\n rangeHeader?: string\n req: PayloadRequest\n uploadConfig: SanitizedUploadConfig\n}): Promise<Response> => {\n const scopedReq = rangeHeader ? withRangeHeader(req, rangeHeader) : req\n\n let response: null | Response = null\n let error: unknown\n\n for (const handler of uploadConfig.handlers!) {\n try {\n const result = await handler(scopedReq, {\n doc: null!,\n params: {\n collection: collectionSlug,\n filename: file.filename,\n uploadReference: file.uploadReference,\n },\n })\n if (result) {\n response = result\n /**\n * - If a handler returns a Response, the response will be sent to the client and no further handlers will be run.\n * - If a handler returns null, the next handler will be run.\n *\n * @see packages/payload/src/uploads/types.ts\n */\n break\n }\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 response = await fetch(redirectUrl, {\n ...(rangeHeader && { headers: { Range: rangeHeader } }),\n })\n }\n }\n\n if (!response.ok) {\n throw new APIError('Uploaded source is not readable.', 400)\n }\n\n return response\n}\n\n/**\n * Overrides `req.headers` with a `Range` header while leaving the real incoming request\n * untouched. Uses a Proxy, rather than cloning `req`, because `req` is a native `Request`\n * instance at runtime - accessors like `.signal` are brand-checked against `req`'s internal\n * slots, so a plain clone (e.g. `Object.create(req)`) throws once a handler reads one of them.\n * Forwarding reads through `Reflect.get(req, prop, req)` keeps `this` bound to the real `req`\n * so those accessors keep working.\n */\nconst withRangeHeader = (req: PayloadRequest, rangeHeader: string): PayloadRequest => {\n const headers = new Headers(req.headers)\n headers.set('range', rangeHeader)\n\n return new Proxy(req, {\n get(target, prop) {\n return prop === 'headers' ? headers : Reflect.get(target, prop, target)\n },\n })\n}\n\n/**\n * Reads at most `maxBytes` from the response body, then cancels the reader - so a handler that\n * ignores the range hint and starts streaming the whole file is still cut short on our end.\n */\nconst readBoundedPrefix = async (response: Response, maxBytes: number): Promise<Buffer> => {\n if (!response.body) {\n return Buffer.alloc(0)\n }\n\n const reader = response.body.getReader()\n const chunks: Buffer[] = []\n let total = 0\n\n try {\n while (total < maxBytes) {\n const { done, value } = await reader.read()\n if (done || !value) {\n break\n }\n\n const remaining = maxBytes - total\n const chunk = value.byteLength > remaining ? value.subarray(0, remaining) : value\n chunks.push(Buffer.from(chunk))\n total += chunk.byteLength\n }\n } finally {\n await reader.cancel().catch(() => {})\n }\n\n return Buffer.concat(chunks, total)\n}\n\n/**\n * Streams the fetched upload straight to disk instead of buffering it in memory. A client upload\n * (e.g. Azure's chunkLargeFiles) can be far larger than the server's available memory, so this\n * avoids re-downloading the whole file into a single in-memory buffer just to read it back out.\n */\nconst streamResponseToTempFile = async ({\n req,\n response,\n}: {\n req: PayloadRequest\n response: Response\n}): Promise<string> => {\n if (!response.body) {\n throw new APIError('Expected a response body from the upload handler.')\n }\n\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 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","stat","os","path","Readable","pipeline","APIError","sanitizeFilename","sanitizeUploadPrefix","verifyClientUploadReceipt","docWithFilenameExists","getOriginalFilename","normalizeStorageKey","getFileContentRequirement","HEADER_PROBE_BYTE_LENGTH","getImageSize","hasCropOrResizeEdit","getStagedFile","planTransformerPipeline","getUploadTransformerInternal","getFileFromUploadInstructions","collectionSlug","file","req","context","_payloadVerifiedProviderOriginal","filename","mimeType","Number","isSafeInteger","size","uploadReference","Array","isArray","uploadConfig","payload","collections","config","upload","allowOverwrite","providerFilename","verifiedOriginal","uploadInstructions","requiresUploadReceipt","signedReceipt","undefined","receipt","originalFilename","storageFilePath","key","expectedKeySuffix","filePrefix","_objectKey","filter","Boolean","join","posix","basename","endsWith","prefix","sanitizedFilename","matchAnyPrefix","handlers","contentRequirement","hasSizeEdits","requestHasSizeEdits","hasTransformFileStages","providerFile","rememberVerifiedOriginal","response","fetchUploadResponse","rangeHeader","assertProviderFileSize","expectedSize","readBoundedPrefix","length","name","data","Buffer","alloc","mimetype","headerFile","fetchHeaderOnly","tempFilePath","streamResponseToTempFile","force","_payloadClientUploadTempFile","headers","get","uploadEdits","query","transformers","args","operation","capability","some","transformer","prepareUpload","headerBuffer","contentRange","reportedSize","status","match","scopedReq","withRangeHeader","error","handler","result","doc","params","collection","err","logger","redirectUrl","fetch","Range","ok","Headers","set","Proxy","target","prop","Reflect","maxBytes","body","reader","getReader","chunks","total","done","value","read","remaining","chunk","byteLength","subarray","push","from","cancel","catch","concat","tempFileDir","tmpdir","recursive","fromWeb","createWriteStream"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/uploads/safeFetch.ts"],"sourcesContent":["import type { LookupFunction } from 'net'\n\nimport { lookup } from 'dns'\nimport ipaddr from 'ipaddr.js'\nimport { Agent, fetch as undiciFetch } from 'undici'\n\n/**\n * @internal this is used to mock the IP `lookup` function in integration tests\n */\nexport const _internal_safeFetchGlobal = {\n lookup,\n}\n\nconst buildBlockedFetchError = (hostname: string): Error =>\n new Error(\n `Blocked unsafe attempt to ${hostname}. This host resolves to a private or internal address. ` +\n `If this is a trusted destination, allowlist it via the upload collection's \"skipSafeFetch\" option ` +\n `(https://payloadcms.com/docs/upload/overview#skip-safe-fetch).`,\n )\n\nconst isSafeIp = (ip: string) => {\n try {\n if (!ip) {\n return false\n }\n\n if (!ipaddr.isValid(ip)) {\n return false\n }\n\n const parsedIpAddress = ipaddr.parse(ip)\n const range = parsedIpAddress.range()\n if (range !== 'unicast') {\n return false // Private IP Range\n }\n } catch (ignore) {\n return false\n }\n return true\n}\n\nconst ssrfFilterInterceptor: LookupFunction = (hostname, options, callback) => {\n _internal_safeFetchGlobal.lookup(hostname, options, (err, address, family) => {\n if (err) {\n callback(err, address, family)\n } else {\n let ips = [] as string[]\n if (Array.isArray(address)) {\n ips = address.map((a) => a.address)\n } else {\n ips = [address]\n }\n\n if (ips.some((ip) => !isSafeIp(ip))) {\n callback(buildBlockedFetchError(hostname), address, family)\n return\n }\n\n callback(null, address, family)\n }\n })\n}\n\nlet safeDispatcher: Agent | undefined\n\nconst getSafeDispatcher = (): Agent => {\n if (!safeDispatcher) {\n safeDispatcher = new Agent({ connect: { lookup: ssrfFilterInterceptor } })\n }\n return safeDispatcher\n}\n\n/**\n * A \"safe\" version of undici's fetch that prevents SSRF attacks.\n *\n * - Utilizes a custom dispatcher that filters out requests to unsafe IP addresses.\n * - Validates domain names by resolving them to IP addresses and checking if they're safe.\n * - Undici was used because it supported interceptors as well as \"credentials: include\". Native fetch\n */\nexport const safeFetch = async (...args: Parameters<typeof undiciFetch>): Promise<Response> => {\n const [unverifiedUrl, options] = args\n\n try {\n const url = new URL(unverifiedUrl)\n\n let hostname = url.hostname\n\n // Strip brackets from IPv6 addresses (e.g., \"[::1]\" => \"::1\")\n if (hostname.startsWith('[') && hostname.endsWith(']')) {\n hostname = hostname.slice(1, -1)\n }\n\n if (ipaddr.isValid(hostname)) {\n if (!isSafeIp(hostname)) {\n throw buildBlockedFetchError(hostname)\n }\n }\n return (await undiciFetch(url, {\n ...options,\n dispatcher: getSafeDispatcher(),\n redirect: 'manual', // Prevent automatic redirects\n })) as unknown as Response\n } catch (error) {\n if (error instanceof Error) {\n if (error.cause instanceof Error && error.cause.message.includes('unsafe')) {\n // Errors thrown from within interceptors always have 'fetch error' as the message\n // The desired message we want to bubble up is in the cause\n throw new Error(error.cause.message)\n } else {\n let stringifiedUrl: string | undefined = undefined\n if (typeof unverifiedUrl === 'string') {\n stringifiedUrl = unverifiedUrl\n } else if (unverifiedUrl instanceof URL) {\n stringifiedUrl = unverifiedUrl.toString()\n } else if (unverifiedUrl instanceof Request) {\n stringifiedUrl = unverifiedUrl.url\n }\n\n throw new Error(`Failed to fetch from ${stringifiedUrl}, ${error.message}`)\n }\n }\n throw error\n }\n}\n"],"names":["lookup","ipaddr","Agent","fetch","undiciFetch","_internal_safeFetchGlobal","buildBlockedFetchError","hostname","Error","isSafeIp","ip","isValid","parsedIpAddress","parse","range","ignore","ssrfFilterInterceptor","options","callback","err","address","family","ips","Array","isArray","map","a","some","safeDispatcher","getSafeDispatcher","connect","safeFetch","args","unverifiedUrl","url","URL","startsWith","endsWith","slice","dispatcher","redirect","error","cause","message","includes","stringifiedUrl","undefined","toString","Request"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/uploads/safeFetch.ts"],"sourcesContent":["import type { LookupFunction } from 'net'\n\nimport { lookup } from 'dns'\nimport ipaddr from 'ipaddr.js'\nimport { Agent, fetch as undiciFetch } from 'undici'\n\n/**\n * @internal this is used to mock the IP `lookup` function in integration tests\n */\nexport const _internal_safeFetchGlobal = {\n lookup,\n}\n\nconst buildBlockedFetchError = (hostname: string): Error =>\n new Error(\n `Blocked unsafe attempt to ${hostname}. This host resolves to a private or internal address. ` +\n `If this is a trusted destination, allowlist it via the upload collection's \"skipSafeFetch\" option ` +\n `(https://payloadcms.com/docs/upload/overview#skip-safe-fetch).`,\n )\n\nconst isSafeIp = (ip: string) => {\n try {\n if (!ip) {\n return false\n }\n\n if (!ipaddr.isValid(ip)) {\n return false\n }\n\n const parsedIpAddress = ipaddr.parse(ip)\n const range = parsedIpAddress.range()\n if (range !== 'unicast') {\n return false // Private IP Range\n }\n } catch (ignore) {\n return false\n }\n return true\n}\n\nconst ssrfFilterInterceptor: LookupFunction = (hostname, options, callback) => {\n _internal_safeFetchGlobal.lookup(hostname, options, (err, address, family) => {\n if (err) {\n callback(err, address, family)\n } else {\n let ips = [] as string[]\n if (Array.isArray(address)) {\n ips = address.map((a) => a.address)\n } else {\n ips = [address]\n }\n\n if (ips.some((ip) => !isSafeIp(ip))) {\n callback(buildBlockedFetchError(hostname), address, family)\n return\n }\n\n callback(null, address, family)\n }\n })\n}\n\nlet safeDispatcher: Agent | undefined\n\nconst getSafeDispatcher = (): Agent => {\n if (!safeDispatcher) {\n safeDispatcher = new Agent({ connect: { lookup: ssrfFilterInterceptor } })\n }\n return safeDispatcher\n}\n\n/**\n * A \"safe\" version of undici's fetch that prevents SSRF attacks.\n *\n * - Utilizes a custom dispatcher that filters out requests to unsafe IP addresses.\n * - Validates domain names by resolving them to IP addresses and checking if they're safe.\n * - Undici was used because it supported interceptors as well as \"credentials: include\". Native fetch\n */\nexport const safeFetch = async (...args: Parameters<typeof undiciFetch>): Promise<Response> => {\n const [unverifiedUrl, options] = args\n\n try {\n const url = new URL(unverifiedUrl)\n\n let hostname = url.hostname\n\n // Strip brackets from IPv6 addresses (e.g., \"[::1]\" => \"::1\")\n if (hostname.startsWith('[') && hostname.endsWith(']')) {\n hostname = hostname.slice(1, -1)\n }\n\n if (ipaddr.isValid(hostname)) {\n if (!isSafeIp(hostname)) {\n throw buildBlockedFetchError(hostname)\n }\n }\n return await undiciFetch(url, {\n ...options,\n dispatcher: getSafeDispatcher(),\n redirect: 'manual', // Prevent automatic redirects\n })\n } catch (error) {\n if (error instanceof Error) {\n if (error.cause instanceof Error && error.cause.message.includes('unsafe')) {\n // Errors thrown from within interceptors always have 'fetch error' as the message\n // The desired message we want to bubble up is in the cause\n throw new Error(error.cause.message)\n } else {\n let stringifiedUrl: string | undefined = undefined\n if (typeof unverifiedUrl === 'string') {\n stringifiedUrl = unverifiedUrl\n } else if (unverifiedUrl instanceof URL) {\n stringifiedUrl = unverifiedUrl.toString()\n } else if (unverifiedUrl instanceof Request) {\n stringifiedUrl = unverifiedUrl.url\n }\n\n throw new Error(`Failed to fetch from ${stringifiedUrl}, ${error.message}`)\n }\n }\n throw error\n }\n}\n"],"names":["lookup","ipaddr","Agent","fetch","undiciFetch","_internal_safeFetchGlobal","buildBlockedFetchError","hostname","Error","isSafeIp","ip","isValid","parsedIpAddress","parse","range","ignore","ssrfFilterInterceptor","options","callback","err","address","family","ips","Array","isArray","map","a","some","safeDispatcher","getSafeDispatcher","connect","safeFetch","args","unverifiedUrl","url","URL","startsWith","endsWith","slice","dispatcher","redirect","error","cause","message","includes","stringifiedUrl","undefined","toString","Request"],"mappings":"AAEA,SAASA,MAAM,QAAQ,MAAK;AAC5B,OAAOC,YAAY,YAAW;AAC9B,SAASC,KAAK,EAAEC,SAASC,WAAW,QAAQ,SAAQ;AAEpD;;CAEC,GACD,OAAO,MAAMC,4BAA4B;IACvCL;AACF,EAAC;AAED,MAAMM,yBAAyB,CAACC,WAC9B,IAAIC,MACF,CAAC,0BAA0B,EAAED,SAAS,uDAAuD,CAAC,GAC5F,CAAC,kGAAkG,CAAC,GACpG,CAAC,8DAA8D,CAAC;AAGtE,MAAME,WAAW,CAACC;IAChB,IAAI;QACF,IAAI,CAACA,IAAI;YACP,OAAO;QACT;QAEA,IAAI,CAACT,OAAOU,OAAO,CAACD,KAAK;YACvB,OAAO;QACT;QAEA,MAAME,kBAAkBX,OAAOY,KAAK,CAACH;QACrC,MAAMI,QAAQF,gBAAgBE,KAAK;QACnC,IAAIA,UAAU,WAAW;YACvB,OAAO,MAAM,mBAAmB;;QAClC;IACF,EAAE,OAAOC,QAAQ;QACf,OAAO;IACT;IACA,OAAO;AACT;AAEA,MAAMC,wBAAwC,CAACT,UAAUU,SAASC;IAChEb,0BAA0BL,MAAM,CAACO,UAAUU,SAAS,CAACE,KAAKC,SAASC;QACjE,IAAIF,KAAK;YACPD,SAASC,KAAKC,SAASC;QACzB,OAAO;YACL,IAAIC,MAAM,EAAE;YACZ,IAAIC,MAAMC,OAAO,CAACJ,UAAU;gBAC1BE,MAAMF,QAAQK,GAAG,CAAC,CAACC,IAAMA,EAAEN,OAAO;YACpC,OAAO;gBACLE,MAAM;oBAACF;iBAAQ;YACjB;YAEA,IAAIE,IAAIK,IAAI,CAAC,CAACjB,KAAO,CAACD,SAASC,MAAM;gBACnCQ,SAASZ,uBAAuBC,WAAWa,SAASC;gBACpD;YACF;YAEAH,SAAS,MAAME,SAASC;QAC1B;IACF;AACF;AAEA,IAAIO;AAEJ,MAAMC,oBAAoB;IACxB,IAAI,CAACD,gBAAgB;QACnBA,iBAAiB,IAAI1B,MAAM;YAAE4B,SAAS;gBAAE9B,QAAQgB;YAAsB;QAAE;IAC1E;IACA,OAAOY;AACT;AAEA;;;;;;CAMC,GACD,OAAO,MAAMG,YAAY,OAAO,GAAGC;IACjC,MAAM,CAACC,eAAehB,QAAQ,GAAGe;IAEjC,IAAI;QACF,MAAME,MAAM,IAAIC,IAAIF;QAEpB,IAAI1B,WAAW2B,IAAI3B,QAAQ;QAE3B,8DAA8D;QAC9D,IAAIA,SAAS6B,UAAU,CAAC,QAAQ7B,SAAS8B,QAAQ,CAAC,MAAM;YACtD9B,WAAWA,SAAS+B,KAAK,CAAC,GAAG,CAAC;QAChC;QAEA,IAAIrC,OAAOU,OAAO,CAACJ,WAAW;YAC5B,IAAI,CAACE,SAASF,WAAW;gBACvB,MAAMD,uBAAuBC;YAC/B;QACF;QACA,OAAO,MAAMH,YAAY8B,KAAK;YAC5B,GAAGjB,OAAO;YACVsB,YAAYV;YACZW,UAAU;QACZ;IACF,EAAE,OAAOC,OAAO;QACd,IAAIA,iBAAiBjC,OAAO;YAC1B,IAAIiC,MAAMC,KAAK,YAAYlC,SAASiC,MAAMC,KAAK,CAACC,OAAO,CAACC,QAAQ,CAAC,WAAW;gBAC1E,kFAAkF;gBAClF,2DAA2D;gBAC3D,MAAM,IAAIpC,MAAMiC,MAAMC,KAAK,CAACC,OAAO;YACrC,OAAO;gBACL,IAAIE,iBAAqCC;gBACzC,IAAI,OAAOb,kBAAkB,UAAU;oBACrCY,iBAAiBZ;gBACnB,OAAO,IAAIA,yBAAyBE,KAAK;oBACvCU,iBAAiBZ,cAAcc,QAAQ;gBACzC,OAAO,IAAId,yBAAyBe,SAAS;oBAC3CH,iBAAiBZ,cAAcC,GAAG;gBACpC;gBAEA,MAAM,IAAI1B,MAAM,CAAC,qBAAqB,EAAEqC,eAAe,EAAE,EAAEJ,MAAME,OAAO,EAAE;YAC5E;QACF;QACA,MAAMF;IACR;AACF,EAAC"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/uploads/stagedUpload.ts"],"sourcesContent":["import { jwtVerify } from 'jose/jwt/verify'\nimport { randomUUID } from 'node:crypto'\nimport fs from 'node:fs/promises'\nimport os from 'node:os'\nimport path from 'node:path'\n\nimport type { PayloadHandler } from '../config/types.js'\nimport type { PayloadRequest } from '../types/index.js'\nimport type { File, UploadInstructions, UploadInstructionsRequest } from './types.js'\n\nimport { jwtSign } from '../auth/jwt.js'\nimport { APIError, Forbidden } from '../errors/index.js'\nimport { formatAdminURL } from '../utilities/formatAdminURL.js'\nimport { checkFileRestrictions } from './checkFileRestrictions.js'\n\n/**\n * Creates a signed, one-hour URL where the client can send the file bytes. The returned file value\n * is later passed to a document create or update request.\n */\nexport const generateStagedUploadInstructions = async ({\n collectionSlug,\n docPrefix,\n filename,\n filesize,\n mimeType,\n req,\n}: { req: PayloadRequest } & UploadInstructionsRequest): Promise<UploadInstructions> => {\n await removeExpiredUploads(req, collectionSlug)\n\n const { token: uploadId } = await jwtSign({\n fieldsToSign: {\n id: randomUUID(),\n collectionSlug,\n docPrefix,\n filename,\n filesize,\n mimeType,\n user: getUser(req),\n },\n secret: req.payload.secret,\n tokenExpiration,\n })\n\n return {\n type: 'http',\n file: {\n filename,\n mimeType,\n size: filesize,\n uploadReference: { uploadId },\n },\n request: {\n headers: {\n 'Content-Length': String(filesize),\n 'Content-Type': mimeType,\n },\n method: 'PUT',\n url: formatAdminURL({\n apiRoute: req.payload.config.routes.api,\n path: `/upload-instructions/${uploadId}`,\n serverURL: req.payload.config.serverURL,\n }),\n },\n }\n}\n\n/**\n * Saves the bytes sent to a signed upload URL. The file is written as a partial file first and only\n * becomes available when its size exactly matches the size in the upload ID.\n */\nexport const uploadStagedFile: PayloadHandler = async (req) => {\n const upload = await verifyUploadID(req, req.routeParams?.uploadId)\n const directory = await getUploadDirectory(req, upload.collectionSlug)\n const uploadPath = path.join(directory, upload.id)\n const temporaryPath = `${uploadPath}.${randomUUID()}.part`\n const file = await fs.open(temporaryPath, 'wx')\n let uploadedSize = 0\n\n try {\n try {\n const reader = req.body?.getReader()\n\n while (reader) {\n const { done, value } = await reader.read()\n\n if (done) {\n break\n }\n\n uploadedSize += value.byteLength\n if (uploadedSize > upload.filesize) {\n throw new APIError('Uploaded file is larger than expected.', 400)\n }\n\n let offset = 0\n while (offset < value.byteLength) {\n const { bytesWritten } = await file.write(value, offset)\n offset += bytesWritten\n }\n }\n\n if (uploadedSize !== upload.filesize) {\n throw new APIError('Uploaded file size does not match the expected size.', 400)\n }\n } finally {\n await file.close()\n }\n\n const collection = req.payload.collections[upload.collectionSlug]!.config\n await checkFileRestrictions({\n collection,\n file: {\n name: upload.filename,\n data: Buffer.alloc(0),\n mimetype: upload.mimeType,\n size: upload.filesize,\n tempFilePath: temporaryPath,\n },\n req,\n })\n\n await fs.rename(temporaryPath, uploadPath)\n } catch (error) {\n await fs.rm(temporaryPath, { force: true })\n throw error\n }\n\n return new Response(null, { status: 204 })\n}\n\n/** Removes a temporary upload that the client no longer needs. */\nexport const deleteStagedFile: PayloadHandler = async (req) => {\n const upload = await verifyUploadID(req, req.routeParams?.uploadId)\n const directory = await getUploadDirectory(req, upload.collectionSlug)\n\n await fs.rm(path.join(directory, upload.id), { force: true })\n\n return new Response(null, { status: 204 })\n}\n\n/**\n * Loads a completed staged file for a document request. Only the same user and collection can use\n * it, and the temporary file is deleted after it is read.\n */\nexport const getStagedFile = async ({\n collectionSlug,\n req,\n uploadReference,\n}: {\n collectionSlug: string\n req: PayloadRequest\n uploadReference: unknown\n}): Promise<File> => {\n if (\n !uploadReference ||\n typeof uploadReference !== 'object' ||\n Array.isArray(uploadReference) ||\n !('uploadId' in uploadReference) ||\n typeof uploadReference.uploadId !== 'string'\n ) {\n throw new APIError('Invalid staged upload.', 400)\n }\n\n const { uploadId } = uploadReference\n const upload = await verifyUploadID(req, uploadId)\n\n if (upload.collectionSlug !== collectionSlug || upload.user !== getUser(req)) {\n throw new Forbidden(req.t)\n }\n\n const directory = await getUploadDirectory(req, upload.collectionSlug)\n const tempFilePath = path.join(directory, upload.id)\n let data: Buffer\n\n try {\n data = await fs.readFile(tempFilePath)\n } catch {\n throw new APIError('Staged upload was not found.', 400)\n }\n\n if (data.length !== upload.filesize) {\n await fs.rm(tempFilePath, { force: true })\n throw new APIError('Staged upload is incomplete.', 400)\n }\n\n await fs.rm(tempFilePath, { force: true })\n\n return {\n name: upload.filename,\n data,\n mimetype: upload.mimeType,\n size: data.length,\n }\n}\n\n/**\n * Staged uploads hold a file temporarily until a document create or update uses it. The signed\n * upload ID remembers which file and user the upload belongs to.\n */\nconst tokenExpiration = 60 * 60\n\n/** The file details stored inside the signed upload ID. */\ntype StagedUploadToken = {\n id: string\n user: null | string\n} & UploadInstructionsRequest\n\nconst getUser = (req: PayloadRequest) =>\n req.user ? `${req.user.collection}:${String(req.user.id)}` : null\n\n/** Returns the hidden folder where temporary uploads for this collection are stored. */\nconst getUploadDirectory = async (req: PayloadRequest, collectionSlug: string) => {\n const upload = req.payload.collections[collectionSlug]?.config.upload\n const directory = path.resolve(\n upload && !upload.disableLocalStorage\n ? upload.staticDir || collectionSlug\n : req.payload.config.upload.tempFileDir || os.tmpdir(),\n '.payload-staged-uploads',\n )\n\n await fs.mkdir(directory, { recursive: true })\n\n return directory\n}\n\n/** Removes temporary files older than the one-hour upload window. */\nconst removeExpiredUploads = async (req: PayloadRequest, collectionSlug: string) => {\n const directory = await getUploadDirectory(req, collectionSlug)\n const expiresBefore = Date.now() - tokenExpiration * 1000\n\n for (const filename of await fs.readdir(directory)) {\n const filePath = path.join(directory, filename)\n const stats = await fs.stat(filePath).catch(() => null)\n\n if (stats && stats.mtimeMs < expiresBefore) {\n await fs.rm(filePath, { force: true })\n }\n }\n}\n\n/** Verifies that an upload ID was signed by Payload, has not expired, and contains valid details. */\nconst verifyUploadID = async (\n req: PayloadRequest,\n uploadID: unknown,\n): Promise<StagedUploadToken> => {\n try {\n if (typeof uploadID !== 'string') {\n throw new Error()\n }\n\n const secret = new TextEncoder().encode(req.payload.secret)\n const { payload } = await jwtVerify<StagedUploadToken>(uploadID, secret)\n\n if (\n typeof payload.id !== 'string' ||\n !/^[\\da-f-]{36}$/i.test(payload.id) ||\n typeof payload.collectionSlug !== 'string' ||\n typeof payload.filename !== 'string' ||\n !Number.isSafeInteger(payload.filesize) ||\n payload.filesize < 0 ||\n typeof payload.mimeType !== 'string' ||\n (payload.user !== null && typeof payload.user !== 'string')\n ) {\n throw new Error()\n }\n\n return payload as StagedUploadToken\n } catch {\n throw new APIError('Invalid or expired staged upload.', 400)\n }\n}\n"],"names":["jwtVerify","randomUUID","fs","os","path","jwtSign","APIError","Forbidden","formatAdminURL","checkFileRestrictions","generateStagedUploadInstructions","collectionSlug","docPrefix","filename","filesize","mimeType","req","removeExpiredUploads","token","uploadId","fieldsToSign","id","user","getUser","secret","payload","tokenExpiration","type","file","size","uploadReference","request","headers","String","method","url","apiRoute","config","routes","api","serverURL","uploadStagedFile","upload","verifyUploadID","routeParams","directory","getUploadDirectory","uploadPath","join","temporaryPath","open","uploadedSize","reader","body","getReader","done","value","read","byteLength","offset","bytesWritten","write","close","collection","collections","name","data","Buffer","alloc","mimetype","tempFilePath","rename","error","rm","force","Response","status","deleteStagedFile","getStagedFile","Array","isArray","t","readFile","length","resolve","disableLocalStorage","staticDir","tempFileDir","tmpdir","mkdir","recursive","expiresBefore","Date","now","readdir","filePath","stats","stat","catch","mtimeMs","uploadID","Error","TextEncoder","encode","test","Number","isSafeInteger"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/uploads/stagedUpload.ts"],"sourcesContent":["import { jwtVerify } from 'jose/jwt/verify'\nimport { randomUUID } from 'node:crypto'\nimport fs from 'node:fs/promises'\nimport os from 'node:os'\nimport path from 'node:path'\n\nimport type { PayloadHandler } from '../config/types.js'\nimport type { PayloadRequest } from '../types/index.js'\nimport type { File, UploadInstructions, UploadInstructionsRequest } from './types.js'\n\nimport { jwtSign } from '../auth/jwt.js'\nimport { APIError, Forbidden } from '../errors/index.js'\nimport { formatAdminURL } from '../utilities/formatAdminURL.js'\nimport { checkFileRestrictions } from './checkFileRestrictions.js'\n\n/**\n * Creates a signed, one-hour URL where the client can send the file bytes. The returned file value\n * is later passed to a document create or update request.\n */\nexport const generateStagedUploadInstructions = async ({\n collectionSlug,\n docPrefix,\n filename,\n filesize,\n mimeType,\n req,\n}: { req: PayloadRequest } & UploadInstructionsRequest): Promise<UploadInstructions> => {\n await removeExpiredUploads(req, collectionSlug)\n\n const { token: uploadId } = await jwtSign({\n fieldsToSign: {\n id: randomUUID(),\n collectionSlug,\n docPrefix,\n filename,\n filesize,\n mimeType,\n user: getUser(req),\n },\n secret: req.payload.secret,\n tokenExpiration,\n })\n\n return {\n type: 'http',\n file: {\n filename,\n mimeType,\n size: filesize,\n uploadReference: { uploadId },\n },\n request: {\n headers: {\n 'Content-Length': String(filesize),\n 'Content-Type': mimeType,\n },\n method: 'PUT',\n url: formatAdminURL({\n apiRoute: req.payload.config.routes.api,\n path: `/upload-instructions/${uploadId}`,\n serverURL: req.payload.config.serverURL,\n }),\n },\n }\n}\n\n/**\n * Saves the bytes sent to a signed upload URL. The file is written as a partial file first and only\n * becomes available when its size exactly matches the size in the upload ID.\n */\nexport const uploadStagedFile: PayloadHandler = async (req) => {\n const upload = await verifyUploadID(req, req.routeParams?.uploadId)\n const directory = await getUploadDirectory(req, upload.collectionSlug)\n const uploadPath = path.join(directory, upload.id)\n const temporaryPath = `${uploadPath}.${randomUUID()}.part`\n const file = await fs.open(temporaryPath, 'wx')\n let uploadedSize = 0\n\n try {\n try {\n const reader = req.body?.getReader()\n\n while (reader) {\n const { done, value } = await reader.read()\n\n if (done) {\n break\n }\n\n uploadedSize += value.byteLength\n if (uploadedSize > upload.filesize) {\n throw new APIError('Uploaded file is larger than expected.', 400)\n }\n\n let offset = 0\n while (offset < value.byteLength) {\n const { bytesWritten } = await file.write(value, offset)\n offset += bytesWritten\n }\n }\n\n if (uploadedSize !== upload.filesize) {\n throw new APIError('Uploaded file size does not match the expected size.', 400)\n }\n } finally {\n await file.close()\n }\n\n const collection = req.payload.collections[upload.collectionSlug]!.config\n await checkFileRestrictions({\n collection,\n file: {\n name: upload.filename,\n data: Buffer.alloc(0),\n mimetype: upload.mimeType,\n size: upload.filesize,\n tempFilePath: temporaryPath,\n },\n req,\n })\n\n await fs.rename(temporaryPath, uploadPath)\n } catch (error) {\n await fs.rm(temporaryPath, { force: true })\n throw error\n }\n\n return new Response(null, { status: 204 })\n}\n\n/** Removes a temporary upload that the client no longer needs. */\nexport const deleteStagedFile: PayloadHandler = async (req) => {\n const upload = await verifyUploadID(req, req.routeParams?.uploadId)\n const directory = await getUploadDirectory(req, upload.collectionSlug)\n\n await fs.rm(path.join(directory, upload.id), { force: true })\n\n return new Response(null, { status: 204 })\n}\n\n/**\n * Loads a completed staged file for a document request. Only the same user and collection can use\n * it, and the temporary file is deleted after it is read.\n */\nexport const getStagedFile = async ({\n collectionSlug,\n req,\n uploadReference,\n}: {\n collectionSlug: string\n req: PayloadRequest\n uploadReference: unknown\n}): Promise<File> => {\n if (\n !uploadReference ||\n typeof uploadReference !== 'object' ||\n Array.isArray(uploadReference) ||\n !('uploadId' in uploadReference) ||\n typeof uploadReference.uploadId !== 'string'\n ) {\n throw new APIError('Invalid staged upload.', 400)\n }\n\n const { uploadId } = uploadReference\n const upload = await verifyUploadID(req, uploadId)\n\n if (upload.collectionSlug !== collectionSlug || upload.user !== getUser(req)) {\n throw new Forbidden(req.t)\n }\n\n const directory = await getUploadDirectory(req, upload.collectionSlug)\n const tempFilePath = path.join(directory, upload.id)\n let data: Buffer\n\n try {\n data = await fs.readFile(tempFilePath)\n } catch {\n throw new APIError('Staged upload was not found.', 400)\n }\n\n if (data.length !== upload.filesize) {\n await fs.rm(tempFilePath, { force: true })\n throw new APIError('Staged upload is incomplete.', 400)\n }\n\n await fs.rm(tempFilePath, { force: true })\n\n return {\n name: upload.filename,\n data,\n mimetype: upload.mimeType,\n size: data.length,\n }\n}\n\n/**\n * Staged uploads hold a file temporarily until a document create or update uses it. The signed\n * upload ID remembers which file and user the upload belongs to.\n */\nconst tokenExpiration = 60 * 60\n\n/** The file details stored inside the signed upload ID. */\ntype StagedUploadToken = {\n id: string\n user: null | string\n} & UploadInstructionsRequest\n\nconst getUser = (req: PayloadRequest) =>\n req.user ? `${req.user.collection}:${String(req.user.id)}` : null\n\n/** Returns the hidden folder where temporary uploads for this collection are stored. */\nconst getUploadDirectory = async (req: PayloadRequest, collectionSlug: string) => {\n const upload = req.payload.collections[collectionSlug]?.config.upload\n const directory = path.resolve(\n upload && !upload.disableLocalStorage\n ? upload.staticDir || collectionSlug\n : req.payload.config.upload.tempFileDir || os.tmpdir(),\n '.payload-staged-uploads',\n )\n\n await fs.mkdir(directory, { recursive: true })\n\n return directory\n}\n\n/** Removes temporary files older than the one-hour upload window. */\nconst removeExpiredUploads = async (req: PayloadRequest, collectionSlug: string) => {\n const directory = await getUploadDirectory(req, collectionSlug)\n const expiresBefore = Date.now() - tokenExpiration * 1000\n\n for (const filename of await fs.readdir(directory)) {\n const filePath = path.join(directory, filename)\n const stats = await fs.stat(filePath).catch(() => null)\n\n if (stats && stats.mtimeMs < expiresBefore) {\n await fs.rm(filePath, { force: true })\n }\n }\n}\n\n/** Verifies that an upload ID was signed by Payload, has not expired, and contains valid details. */\nconst verifyUploadID = async (\n req: PayloadRequest,\n uploadID: unknown,\n): Promise<StagedUploadToken> => {\n try {\n if (typeof uploadID !== 'string') {\n throw new Error()\n }\n\n const secret = new TextEncoder().encode(req.payload.secret)\n const { payload } = await jwtVerify<StagedUploadToken>(uploadID, secret)\n\n if (\n typeof payload.id !== 'string' ||\n !/^[\\da-f-]{36}$/i.test(payload.id) ||\n typeof payload.collectionSlug !== 'string' ||\n typeof payload.filename !== 'string' ||\n !Number.isSafeInteger(payload.filesize) ||\n payload.filesize < 0 ||\n typeof payload.mimeType !== 'string' ||\n (payload.user !== null && typeof payload.user !== 'string')\n ) {\n throw new Error()\n }\n\n return payload\n } catch {\n throw new APIError('Invalid or expired staged upload.', 400)\n }\n}\n"],"names":["jwtVerify","randomUUID","fs","os","path","jwtSign","APIError","Forbidden","formatAdminURL","checkFileRestrictions","generateStagedUploadInstructions","collectionSlug","docPrefix","filename","filesize","mimeType","req","removeExpiredUploads","token","uploadId","fieldsToSign","id","user","getUser","secret","payload","tokenExpiration","type","file","size","uploadReference","request","headers","String","method","url","apiRoute","config","routes","api","serverURL","uploadStagedFile","upload","verifyUploadID","routeParams","directory","getUploadDirectory","uploadPath","join","temporaryPath","open","uploadedSize","reader","body","getReader","done","value","read","byteLength","offset","bytesWritten","write","close","collection","collections","name","data","Buffer","alloc","mimetype","tempFilePath","rename","error","rm","force","Response","status","deleteStagedFile","getStagedFile","Array","isArray","t","readFile","length","resolve","disableLocalStorage","staticDir","tempFileDir","tmpdir","mkdir","recursive","expiresBefore","Date","now","readdir","filePath","stats","stat","catch","mtimeMs","uploadID","Error","TextEncoder","encode","test","Number","isSafeInteger"],"mappings":"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"}
@@ -119,7 +119,7 @@ import { withFileTransformAccessContext } from './withFileTransformAccessContext
119
119
  if (result.response) {
120
120
  if (handedOutResponse && result.response !== handedOutResponse) {
121
121
  await discardResponse(handedOutResponse);
122
- } else if (previousResponse && result.response !== previousResponse) {
122
+ } else if (!handedOutResponse && previousResponse && result.response !== previousResponse) {
123
123
  await discardResponse(previousResponse);
124
124
  }
125
125
  currentResponse = result.response;
@@ -169,7 +169,16 @@ import { withFileTransformAccessContext } from './withFileTransformAccessContext
169
169
  if (!response.body) {
170
170
  return response;
171
171
  }
172
- return new Response(response.body.pipeThrough(new TransformStream(), {
172
+ const stream = new TransformStream({
173
+ start (controller) {
174
+ // Pipe abort waits for pending writes, which can stall on an abandoned reader.
175
+ // Error the transform to unblock those writes before the pipe cancels its source.
176
+ signal.addEventListener('abort', ()=>controller.error(signal.reason), {
177
+ once: true
178
+ });
179
+ }
180
+ });
181
+ return new Response(response.body.pipeThrough(stream, {
173
182
  signal
174
183
  }), {
175
184
  headers: response.headers,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/uploads/transformers/handleDynamicFileRequest.ts"],"sourcesContent":["import type { Collection } from '../../collections/config/types.js'\nimport type { PayloadRequest } from '../../types/index.js'\nimport type { ResolvedUploadDocument } from './resolveUploadDocument.js'\nimport type { UploadTransformer } from './types.js'\n\nimport { Forbidden } from '../../errors/Forbidden.js'\nimport { NotFound } from '../../errors/NotFound.js'\nimport { TransformerContractError } from '../../errors/TransformerContractError.js'\nimport { checkFileAccess } from '../checkFileAccess.js'\nimport { retrieveFileResponse } from '../endpoints/getFile.js'\nimport { createLazySourceGetter } from './createLazySourceGetter.js'\nimport { finalizeFileResponse } from './finalizeFileResponse.js'\nimport { getSourceFileResponse } from './getSourceFileResponse.js'\nimport { getCandidateTransformers, planTransformerPipeline } from './planTransformerPipeline.js'\nimport { getRequestedFile, resolveUploadDocument } from './resolveUploadDocument.js'\nimport { withFileTransformAccessContext } from './withFileTransformAccessContext.js'\n\n/**\n * Orchestrates a dynamic file request end to end: resolve the document, plan the\n * request-capable transformer pipeline from the requested file's authoritative\n * MIME type (the primary file or the matched image size), enforce\n * transform-aware read access, then run every eligible transformer in declaration\n * order against a lazily-fetched source. Called from `getFileHandler` only when at\n * least one transformer is configured — the zero-transformer path never reaches\n * this function.\n */\nexport async function handleDynamicFileRequest({\n collection,\n filename,\n prefix,\n req,\n}: {\n collection: Collection\n filename: string\n prefix?: string\n req: PayloadRequest\n}): Promise<Response> {\n const resolvedDocument = await resolveUploadDocument({ collection, filename, prefix, req })\n\n if (!resolvedDocument) {\n // There's no mimeType to plan a pipeline from, so both access modes must pass\n // before admitting the file doesn't exist — otherwise the status code would leak\n // file existence to an access function keyed on req.fileTransform.\n for (const isTransform of [true, false]) {\n await withFileTransformAccessContext({\n callback: () => checkFileAccess({ collection, filename, prefix, req }),\n isTransform,\n req,\n })\n }\n throw new NotFound(req.t)\n }\n\n if (\n resolvedDocument.original?.filename === filename &&\n (resolvedDocument.filename !== filename || req.searchParams.size === 0)\n ) {\n const permittedDocument = await withFileTransformAccessContext({\n callback: () => checkFileAccess({ collection, filename, prefix, req }),\n isTransform: false,\n req,\n })\n\n return retrieveFileResponse({\n collection,\n doc: (permittedDocument ?? resolvedDocument) as ResolvedUploadDocument,\n filename,\n prefix,\n req,\n })\n }\n\n const { document, pipeline } = await authorizeDocument({\n collection,\n filename,\n prefix,\n req,\n resolvedDocument,\n })\n\n const requestedFile = getRequestedFile({ document, filename })\n\n const source = createLazySourceGetter({\n retrieve: () => getSourceFileResponse({ collection, document, filename, prefix, req }),\n })\n\n let currentResponse: Response | undefined\n // Each stage has its own controller so a discarded body can be closed even when the\n // transformer locked it with a reader. Every remaining controller is aborted on failure.\n const handedOutBodyControllers = new Map<Response, AbortController>()\n\n const discardResponse = async (response: Response): Promise<void> => {\n await cancelUnusedBody(response)\n handedOutBodyControllers.get(response)?.abort()\n handedOutBodyControllers.delete(response)\n }\n\n try {\n for (const transformer of pipeline) {\n const previousResponse = currentResponse\n let handedOutResponse: Response | undefined\n const handedOutBodyController = new AbortController()\n const stageSource = createLazySourceGetter({\n retrieve: async () => {\n handedOutResponse = withAbortableBody({\n response: currentResponse ?? (await source.get()),\n signal: handedOutBodyController.signal,\n })\n handedOutBodyControllers.set(handedOutResponse, handedOutBodyController)\n\n return handedOutResponse\n },\n })\n\n const result = await transformer.handleRequest!({\n collectionSlug: collection.config.slug,\n documentID: document.id,\n filename: requestedFile.filename,\n getSourceFile: stageSource.get,\n mimeType: requestedFile.mimeType,\n req,\n })\n\n if (result.response) {\n if (handedOutResponse && result.response !== handedOutResponse) {\n await discardResponse(handedOutResponse)\n } else if (previousResponse && result.response !== previousResponse) {\n await discardResponse(previousResponse)\n }\n\n currentResponse = result.response\n } else if (stageSource.wasCalled()) {\n throw new TransformerContractError(\n 'A transformer that consumes its source must return a response.',\n )\n }\n\n if (result.status === 'complete') {\n return finalizeFileResponse({ collection, req, response: currentResponse! })\n }\n }\n } catch (err) {\n req.payload.logger.error({ err, msg: 'Error running the file transformer pipeline' })\n for (const handedOutBodyController of handedOutBodyControllers.values()) {\n handedOutBodyController.abort(err)\n }\n await cancelUnusedBody(currentResponse)\n throw err\n }\n\n if (currentResponse) {\n return finalizeFileResponse({ collection, req, response: currentResponse })\n }\n\n // No transformer produced a response — serve the original file through the\n // normal path (Range/ETag/redirect support, existing `modifyResponseHeaders` order).\n return retrieveFileResponse({ collection, doc: document, filename, prefix, req })\n}\n\n/**\n * Re-wraps `response` so its body can be cancelled through `signal`, closing the underlying\n * source stream (file handle, storage connection) regardless of who holds a reader on it.\n */\nfunction withAbortableBody({\n response,\n signal,\n}: {\n response: Response\n signal: AbortSignal\n}): Response {\n if (!response.body) {\n return response\n }\n\n return new Response(response.body.pipeThrough(new TransformStream(), { signal }), {\n headers: response.headers,\n status: response.status,\n statusText: response.statusText,\n })\n}\n\n/**\n * Cancels a response body nothing has started reading. A locked body was handed to a transformer\n * through `withAbortableBody`, and is closed by aborting its signal instead.\n */\nasync function cancelUnusedBody(response: Response | undefined): Promise<void> {\n if (!response?.body || response.body.locked) {\n return\n }\n\n try {\n await response.body.cancel()\n } catch {\n // A clean-up failure must not replace the pipeline result or error.\n }\n}\n\nfunction planRequestPipeline({\n collection,\n document,\n filename,\n req,\n}: {\n collection: Collection\n document: ResolvedUploadDocument\n filename: string\n req: PayloadRequest\n}): Promise<UploadTransformer[]> {\n return planTransformerPipeline({\n args: {\n collectionSlug: collection.config.slug,\n documentID: document.id,\n mimeType: getRequestedFile({ document, filename }).mimeType,\n operation: 'request',\n req,\n },\n capability: 'handleRequest',\n transformers: req.payload.config.upload.transformers,\n })\n}\n\ntype AccessResult = { document?: ResolvedUploadDocument; isAllowed: true } | { isAllowed: false }\n\n/**\n * Decides which document is served and which pipeline runs, checking read access\n * before any application-defined transformer code (`canTransform`) executes.\n *\n * 1. The first access mode comes from MIME matching alone: a transform-aware check\n * when any transformer could handle the file, otherwise an ordinary read.\n * 2. If the transform-aware check is denied, an ordinary read may still be allowed\n * (e.g. public originals, signed-in-only variants), so that mode is tried next.\n * 3. Only once a check has passed does `canTransform` plan the real pipeline.\n * 4. The access mode the final pipeline requires must itself have passed — a\n * \"variants only\" policy must not leak the original when no transformer applies.\n *\n * `resolveUploadDocument` is unfiltered, so without a `prefix` it can match a\n * document the user can't read that shares the filename (filenames are only\n * unique per prefix). Whenever `checkFileAccess` returns a document, that\n * authorized document is the one served — storage adapters trust `doc` to\n * resolve the object key.\n */\nasync function authorizeDocument({\n collection,\n filename,\n prefix,\n req,\n resolvedDocument,\n}: {\n collection: Collection\n filename: string\n prefix?: string\n req: PayloadRequest\n resolvedDocument: ResolvedUploadDocument\n}): Promise<{ document: ResolvedUploadDocument; pipeline: UploadTransformer[] }> {\n const accessResults = new Map<boolean, Promise<AccessResult>>()\n\n const checkAccess = ({ isTransform }: { isTransform: boolean }): Promise<AccessResult> => {\n if (!accessResults.has(isTransform)) {\n accessResults.set(\n isTransform,\n withFileTransformAccessContext({\n callback: () => checkFileAccess({ collection, filename, prefix, req }),\n isTransform,\n req,\n }).then(\n (document) => ({\n document: document as ResolvedUploadDocument | undefined,\n isAllowed: true as const,\n }),\n (err: unknown) => {\n if (err instanceof Forbidden) {\n return { isAllowed: false as const }\n }\n\n throw err\n },\n ),\n )\n }\n\n return accessResults.get(isTransform)!\n }\n\n const hasCandidateTransformers =\n getCandidateTransformers({\n capability: 'handleRequest',\n mimeType: getRequestedFile({ document: resolvedDocument, filename }).mimeType,\n transformers: req.payload.config.upload.transformers,\n }).length > 0\n\n let isTransformAccess = hasCandidateTransformers\n let access = await checkAccess({ isTransform: isTransformAccess })\n\n if (!access.isAllowed && isTransformAccess) {\n isTransformAccess = false\n access = await checkAccess({ isTransform: isTransformAccess })\n }\n\n if (!access.isAllowed) {\n throw new Forbidden(req.t)\n }\n\n const document =\n access.document && access.document.id !== resolvedDocument.id\n ? access.document\n : resolvedDocument\n\n const pipeline = await planRequestPipeline({ collection, document, filename, req })\n const isTransform = pipeline.length > 0\n\n if (isTransform !== isTransformAccess) {\n const requiredAccess = await checkAccess({ isTransform })\n\n if (\n !requiredAccess.isAllowed ||\n (requiredAccess.document && requiredAccess.document.id !== document.id)\n ) {\n throw new Forbidden(req.t)\n }\n }\n\n return { document, pipeline }\n}\n"],"names":["Forbidden","NotFound","TransformerContractError","checkFileAccess","retrieveFileResponse","createLazySourceGetter","finalizeFileResponse","getSourceFileResponse","getCandidateTransformers","planTransformerPipeline","getRequestedFile","resolveUploadDocument","withFileTransformAccessContext","handleDynamicFileRequest","collection","filename","prefix","req","resolvedDocument","isTransform","callback","t","original","searchParams","size","permittedDocument","doc","document","pipeline","authorizeDocument","requestedFile","source","retrieve","currentResponse","handedOutBodyControllers","Map","discardResponse","response","cancelUnusedBody","get","abort","delete","transformer","previousResponse","handedOutResponse","handedOutBodyController","AbortController","stageSource","withAbortableBody","signal","set","result","handleRequest","collectionSlug","config","slug","documentID","id","getSourceFile","mimeType","wasCalled","status","err","payload","logger","error","msg","values","body","Response","pipeThrough","TransformStream","headers","statusText","locked","cancel","planRequestPipeline","args","operation","capability","transformers","upload","accessResults","checkAccess","has","then","isAllowed","hasCandidateTransformers","length","isTransformAccess","access","requiredAccess"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/uploads/transformers/handleDynamicFileRequest.ts"],"sourcesContent":["import type { Collection } from '../../collections/config/types.js'\nimport type { PayloadRequest } from '../../types/index.js'\nimport type { ResolvedUploadDocument } from './resolveUploadDocument.js'\nimport type { UploadTransformer } from './types.js'\n\nimport { Forbidden } from '../../errors/Forbidden.js'\nimport { NotFound } from '../../errors/NotFound.js'\nimport { TransformerContractError } from '../../errors/TransformerContractError.js'\nimport { checkFileAccess } from '../checkFileAccess.js'\nimport { retrieveFileResponse } from '../endpoints/getFile.js'\nimport { createLazySourceGetter } from './createLazySourceGetter.js'\nimport { finalizeFileResponse } from './finalizeFileResponse.js'\nimport { getSourceFileResponse } from './getSourceFileResponse.js'\nimport { getCandidateTransformers, planTransformerPipeline } from './planTransformerPipeline.js'\nimport { getRequestedFile, resolveUploadDocument } from './resolveUploadDocument.js'\nimport { withFileTransformAccessContext } from './withFileTransformAccessContext.js'\n\n/**\n * Orchestrates a dynamic file request end to end: resolve the document, plan the\n * request-capable transformer pipeline from the requested file's authoritative\n * MIME type (the primary file or the matched image size), enforce\n * transform-aware read access, then run every eligible transformer in declaration\n * order against a lazily-fetched source. Called from `getFileHandler` only when at\n * least one transformer is configured — the zero-transformer path never reaches\n * this function.\n */\nexport async function handleDynamicFileRequest({\n collection,\n filename,\n prefix,\n req,\n}: {\n collection: Collection\n filename: string\n prefix?: string\n req: PayloadRequest\n}): Promise<Response> {\n const resolvedDocument = await resolveUploadDocument({ collection, filename, prefix, req })\n\n if (!resolvedDocument) {\n // There's no mimeType to plan a pipeline from, so both access modes must pass\n // before admitting the file doesn't exist — otherwise the status code would leak\n // file existence to an access function keyed on req.fileTransform.\n for (const isTransform of [true, false]) {\n await withFileTransformAccessContext({\n callback: () => checkFileAccess({ collection, filename, prefix, req }),\n isTransform,\n req,\n })\n }\n throw new NotFound(req.t)\n }\n\n if (\n resolvedDocument.original?.filename === filename &&\n (resolvedDocument.filename !== filename || req.searchParams.size === 0)\n ) {\n const permittedDocument = await withFileTransformAccessContext({\n callback: () => checkFileAccess({ collection, filename, prefix, req }),\n isTransform: false,\n req,\n })\n\n return retrieveFileResponse({\n collection,\n doc: permittedDocument ?? resolvedDocument,\n filename,\n prefix,\n req,\n })\n }\n\n const { document, pipeline } = await authorizeDocument({\n collection,\n filename,\n prefix,\n req,\n resolvedDocument,\n })\n\n const requestedFile = getRequestedFile({ document, filename })\n\n const source = createLazySourceGetter({\n retrieve: () => getSourceFileResponse({ collection, document, filename, prefix, req }),\n })\n\n let currentResponse: Response | undefined\n // Each stage has its own controller so a discarded body can be closed even when the\n // transformer locked it with a reader. Every remaining controller is aborted on failure.\n const handedOutBodyControllers = new Map<Response, AbortController>()\n\n const discardResponse = async (response: Response): Promise<void> => {\n await cancelUnusedBody(response)\n handedOutBodyControllers.get(response)?.abort()\n handedOutBodyControllers.delete(response)\n }\n\n try {\n for (const transformer of pipeline) {\n const previousResponse = currentResponse\n let handedOutResponse: Response | undefined\n const handedOutBodyController = new AbortController()\n const stageSource = createLazySourceGetter({\n retrieve: async () => {\n handedOutResponse = withAbortableBody({\n response: currentResponse ?? (await source.get()),\n signal: handedOutBodyController.signal,\n })\n handedOutBodyControllers.set(handedOutResponse, handedOutBodyController)\n\n return handedOutResponse\n },\n })\n\n const result = await transformer.handleRequest!({\n collectionSlug: collection.config.slug,\n documentID: document.id,\n filename: requestedFile.filename,\n getSourceFile: stageSource.get,\n mimeType: requestedFile.mimeType,\n req,\n })\n\n if (result.response) {\n if (handedOutResponse && result.response !== handedOutResponse) {\n await discardResponse(handedOutResponse)\n } else if (!handedOutResponse && previousResponse && result.response !== previousResponse) {\n await discardResponse(previousResponse)\n }\n\n currentResponse = result.response\n } else if (stageSource.wasCalled()) {\n throw new TransformerContractError(\n 'A transformer that consumes its source must return a response.',\n )\n }\n\n if (result.status === 'complete') {\n return finalizeFileResponse({ collection, req, response: currentResponse! })\n }\n }\n } catch (err) {\n req.payload.logger.error({ err, msg: 'Error running the file transformer pipeline' })\n for (const handedOutBodyController of handedOutBodyControllers.values()) {\n handedOutBodyController.abort(err)\n }\n await cancelUnusedBody(currentResponse)\n throw err\n }\n\n if (currentResponse) {\n return finalizeFileResponse({ collection, req, response: currentResponse })\n }\n\n // No transformer produced a response — serve the original file through the\n // normal path (Range/ETag/redirect support, existing `modifyResponseHeaders` order).\n return retrieveFileResponse({ collection, doc: document, filename, prefix, req })\n}\n\n/**\n * Re-wraps `response` so its body can be cancelled through `signal`, closing the underlying\n * source stream (file handle, storage connection) regardless of who holds a reader on it.\n */\nfunction withAbortableBody({\n response,\n signal,\n}: {\n response: Response\n signal: AbortSignal\n}): Response {\n if (!response.body) {\n return response\n }\n\n const stream = new TransformStream({\n start(controller) {\n // Pipe abort waits for pending writes, which can stall on an abandoned reader.\n // Error the transform to unblock those writes before the pipe cancels its source.\n signal.addEventListener('abort', () => controller.error(signal.reason), { once: true })\n },\n })\n\n return new Response(response.body.pipeThrough(stream, { signal }), {\n headers: response.headers,\n status: response.status,\n statusText: response.statusText,\n })\n}\n\n/**\n * Cancels a response body nothing has started reading. A locked body was handed to a transformer\n * through `withAbortableBody`, and is closed by aborting its signal instead.\n */\nasync function cancelUnusedBody(response: Response | undefined): Promise<void> {\n if (!response?.body || response.body.locked) {\n return\n }\n\n try {\n await response.body.cancel()\n } catch {\n // A clean-up failure must not replace the pipeline result or error.\n }\n}\n\nfunction planRequestPipeline({\n collection,\n document,\n filename,\n req,\n}: {\n collection: Collection\n document: ResolvedUploadDocument\n filename: string\n req: PayloadRequest\n}): Promise<UploadTransformer[]> {\n return planTransformerPipeline({\n args: {\n collectionSlug: collection.config.slug,\n documentID: document.id,\n mimeType: getRequestedFile({ document, filename }).mimeType,\n operation: 'request',\n req,\n },\n capability: 'handleRequest',\n transformers: req.payload.config.upload.transformers,\n })\n}\n\ntype AccessResult = { document?: ResolvedUploadDocument; isAllowed: true } | { isAllowed: false }\n\n/**\n * Decides which document is served and which pipeline runs, checking read access\n * before any application-defined transformer code (`canTransform`) executes.\n *\n * 1. The first access mode comes from MIME matching alone: a transform-aware check\n * when any transformer could handle the file, otherwise an ordinary read.\n * 2. If the transform-aware check is denied, an ordinary read may still be allowed\n * (e.g. public originals, signed-in-only variants), so that mode is tried next.\n * 3. Only once a check has passed does `canTransform` plan the real pipeline.\n * 4. The access mode the final pipeline requires must itself have passed — a\n * \"variants only\" policy must not leak the original when no transformer applies.\n *\n * `resolveUploadDocument` is unfiltered, so without a `prefix` it can match a\n * document the user can't read that shares the filename (filenames are only\n * unique per prefix). Whenever `checkFileAccess` returns a document, that\n * authorized document is the one served — storage adapters trust `doc` to\n * resolve the object key.\n */\nasync function authorizeDocument({\n collection,\n filename,\n prefix,\n req,\n resolvedDocument,\n}: {\n collection: Collection\n filename: string\n prefix?: string\n req: PayloadRequest\n resolvedDocument: ResolvedUploadDocument\n}): Promise<{ document: ResolvedUploadDocument; pipeline: UploadTransformer[] }> {\n const accessResults = new Map<boolean, Promise<AccessResult>>()\n\n const checkAccess = ({ isTransform }: { isTransform: boolean }): Promise<AccessResult> => {\n if (!accessResults.has(isTransform)) {\n accessResults.set(\n isTransform,\n withFileTransformAccessContext({\n callback: () => checkFileAccess({ collection, filename, prefix, req }),\n isTransform,\n req,\n }).then(\n (document) => ({\n document: document as ResolvedUploadDocument | undefined,\n isAllowed: true as const,\n }),\n (err: unknown) => {\n if (err instanceof Forbidden) {\n return { isAllowed: false as const }\n }\n\n throw err\n },\n ),\n )\n }\n\n return accessResults.get(isTransform)!\n }\n\n const hasCandidateTransformers =\n getCandidateTransformers({\n capability: 'handleRequest',\n mimeType: getRequestedFile({ document: resolvedDocument, filename }).mimeType,\n transformers: req.payload.config.upload.transformers,\n }).length > 0\n\n let isTransformAccess = hasCandidateTransformers\n let access = await checkAccess({ isTransform: isTransformAccess })\n\n if (!access.isAllowed && isTransformAccess) {\n isTransformAccess = false\n access = await checkAccess({ isTransform: isTransformAccess })\n }\n\n if (!access.isAllowed) {\n throw new Forbidden(req.t)\n }\n\n const document =\n access.document && access.document.id !== resolvedDocument.id\n ? access.document\n : resolvedDocument\n\n const pipeline = await planRequestPipeline({ collection, document, filename, req })\n const isTransform = pipeline.length > 0\n\n if (isTransform !== isTransformAccess) {\n const requiredAccess = await checkAccess({ isTransform })\n\n if (\n !requiredAccess.isAllowed ||\n (requiredAccess.document && requiredAccess.document.id !== document.id)\n ) {\n throw new Forbidden(req.t)\n }\n }\n\n return { document, pipeline }\n}\n"],"names":["Forbidden","NotFound","TransformerContractError","checkFileAccess","retrieveFileResponse","createLazySourceGetter","finalizeFileResponse","getSourceFileResponse","getCandidateTransformers","planTransformerPipeline","getRequestedFile","resolveUploadDocument","withFileTransformAccessContext","handleDynamicFileRequest","collection","filename","prefix","req","resolvedDocument","isTransform","callback","t","original","searchParams","size","permittedDocument","doc","document","pipeline","authorizeDocument","requestedFile","source","retrieve","currentResponse","handedOutBodyControllers","Map","discardResponse","response","cancelUnusedBody","get","abort","delete","transformer","previousResponse","handedOutResponse","handedOutBodyController","AbortController","stageSource","withAbortableBody","signal","set","result","handleRequest","collectionSlug","config","slug","documentID","id","getSourceFile","mimeType","wasCalled","status","err","payload","logger","error","msg","values","body","stream","TransformStream","start","controller","addEventListener","reason","once","Response","pipeThrough","headers","statusText","locked","cancel","planRequestPipeline","args","operation","capability","transformers","upload","accessResults","checkAccess","has","then","isAllowed","hasCandidateTransformers","length","isTransformAccess","access","requiredAccess"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"addSelectGenericsToGeneretedTypes.d.ts","sourceRoot":"","sources":["../../src/utilities/addSelectGenericsToGeneretedTypes.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iCAAiC,gCAE3C;IACD,sBAAsB,EAAE,MAAM,CAAA;CAC/B,WAoDA,CAAA"}
1
+ {"version":3,"file":"addSelectGenericsToGeneretedTypes.d.ts","sourceRoot":"","sources":["../../src/utilities/addSelectGenericsToGeneretedTypes.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iCAAiC,gCAE3C;IACD,sBAAsB,EAAE,MAAM,CAAA;CAC/B,WAmDA,CAAA"}
@@ -17,7 +17,6 @@ export const addSelectGenericsToGeneratedTypes = ({ compiledGeneratedTypes })=>{
17
17
  });
18
18
  }
19
19
  }
20
- // eslint-disable-next-line regexp/no-unused-capturing-group
21
20
  if (line.match(/via the `definition` "([\w-]+_select)"/g)) {
22
21
  isSelectTypeToken = true;
23
22
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utilities/addSelectGenericsToGeneretedTypes.ts"],"sourcesContent":["export const addSelectGenericsToGeneratedTypes = ({\n compiledGeneratedTypes,\n}: {\n compiledGeneratedTypes: string\n}) => {\n const modifiedLines: string[] = []\n\n let isCollectionsSelectToken = false\n let isSelectTypeToken = false\n\n for (const line of compiledGeneratedTypes.split('\\n')) {\n let newLine = line\n if (line === ` collectionsSelect: {` || line === ` globalsSelect: {`) {\n isCollectionsSelectToken = true\n }\n\n if (isCollectionsSelectToken) {\n if (line === ' };') {\n isCollectionsSelectToken = false\n } else {\n // replace <posts: PostsSelect;> with <posts: PostsSelect<true> | PostsSelect<false;>\n newLine = line.replace(/(['\"]?\\w+['\"]?):\\s*(\\w+);/g, (_, variable, type) => {\n return `${variable}: ${type}<false> | ${type}<true>;`\n })\n }\n }\n\n // eslint-disable-next-line regexp/no-unused-capturing-group\n if (line.match(/via the `definition` \"([\\w-]+_select)\"/g)) {\n isSelectTypeToken = true\n }\n\n if (isSelectTypeToken) {\n if (line.startsWith('export interface')) {\n // add generic to the interface\n newLine = line.replace(/(export interface\\s+\\w+)(\\s*\\{)/g, '$1<T extends boolean = true>$2')\n } else {\n newLine = line\n // replace booleans with T on the line\n .replace(/(?<!\\?)\\bboolean\\b/g, 'T')\n // replace interface names like CtaBlock to CtaBlock<T>\n .replace(\n /\\b(\\w+)\\s*\\|\\s*(\\w+)\\b/g,\n (_match, left, right) => `${left} | ${right}<${left}>`,\n )\n\n if (line === '}') {\n isSelectTypeToken = false\n }\n }\n }\n\n modifiedLines.push(newLine)\n }\n\n return modifiedLines.join('\\n')\n}\n"],"names":["addSelectGenericsToGeneratedTypes","compiledGeneratedTypes","modifiedLines","isCollectionsSelectToken","isSelectTypeToken","line","split","newLine","replace","_","variable","type","match","startsWith","_match","left","right","push","join"],"mappings":"AAAA,OAAO,MAAMA,oCAAoC,CAAC,EAChDC,sBAAsB,EAGvB;IACC,MAAMC,gBAA0B,EAAE;IAElC,IAAIC,2BAA2B;IAC/B,IAAIC,oBAAoB;IAExB,KAAK,MAAMC,QAAQJ,uBAAuBK,KAAK,CAAC,MAAO;QACrD,IAAIC,UAAUF;QACd,IAAIA,SAAS,CAAC,sBAAsB,CAAC,IAAIA,SAAS,CAAC,kBAAkB,CAAC,EAAE;YACtEF,2BAA2B;QAC7B;QAEA,IAAIA,0BAA0B;YAC5B,IAAIE,SAAS,QAAQ;gBACnBF,2BAA2B;YAC7B,OAAO;gBACL,qFAAqF;gBACrFI,UAAUF,KAAKG,OAAO,CAAC,8BAA8B,CAACC,GAAGC,UAAUC;oBACjE,OAAO,GAAGD,SAAS,EAAE,EAAEC,KAAK,UAAU,EAAEA,KAAK,OAAO,CAAC;gBACvD;YACF;QACF;QAEA,4DAA4D;QAC5D,IAAIN,KAAKO,KAAK,CAAC,4CAA4C;YACzDR,oBAAoB;QACtB;QAEA,IAAIA,mBAAmB;YACrB,IAAIC,KAAKQ,UAAU,CAAC,qBAAqB;gBACvC,+BAA+B;gBAC/BN,UAAUF,KAAKG,OAAO,CAAC,oCAAoC;YAC7D,OAAO;gBACLD,UAAUF,IACR,sCAAsC;iBACrCG,OAAO,CAAC,uBAAuB,IAChC,uDAAuD;iBACtDA,OAAO,CACN,2BACA,CAACM,QAAQC,MAAMC,QAAU,GAAGD,KAAK,GAAG,EAAEC,MAAM,CAAC,EAAED,KAAK,CAAC,CAAC;gBAG1D,IAAIV,SAAS,KAAK;oBAChBD,oBAAoB;gBACtB;YACF;QACF;QAEAF,cAAce,IAAI,CAACV;IACrB;IAEA,OAAOL,cAAcgB,IAAI,CAAC;AAC5B,EAAC"}
1
+ {"version":3,"sources":["../../src/utilities/addSelectGenericsToGeneretedTypes.ts"],"sourcesContent":["export const addSelectGenericsToGeneratedTypes = ({\n compiledGeneratedTypes,\n}: {\n compiledGeneratedTypes: string\n}) => {\n const modifiedLines: string[] = []\n\n let isCollectionsSelectToken = false\n let isSelectTypeToken = false\n\n for (const line of compiledGeneratedTypes.split('\\n')) {\n let newLine = line\n if (line === ` collectionsSelect: {` || line === ` globalsSelect: {`) {\n isCollectionsSelectToken = true\n }\n\n if (isCollectionsSelectToken) {\n if (line === ' };') {\n isCollectionsSelectToken = false\n } else {\n // replace <posts: PostsSelect;> with <posts: PostsSelect<true> | PostsSelect<false;>\n newLine = line.replace(/(['\"]?\\w+['\"]?):\\s*(\\w+);/g, (_, variable, type) => {\n return `${variable}: ${type}<false> | ${type}<true>;`\n })\n }\n }\n\n if (line.match(/via the `definition` \"([\\w-]+_select)\"/g)) {\n isSelectTypeToken = true\n }\n\n if (isSelectTypeToken) {\n if (line.startsWith('export interface')) {\n // add generic to the interface\n newLine = line.replace(/(export interface\\s+\\w+)(\\s*\\{)/g, '$1<T extends boolean = true>$2')\n } else {\n newLine = line\n // replace booleans with T on the line\n .replace(/(?<!\\?)\\bboolean\\b/g, 'T')\n // replace interface names like CtaBlock to CtaBlock<T>\n .replace(\n /\\b(\\w+)\\s*\\|\\s*(\\w+)\\b/g,\n (_match, left, right) => `${left} | ${right}<${left}>`,\n )\n\n if (line === '}') {\n isSelectTypeToken = false\n }\n }\n }\n\n modifiedLines.push(newLine)\n }\n\n return modifiedLines.join('\\n')\n}\n"],"names":["addSelectGenericsToGeneratedTypes","compiledGeneratedTypes","modifiedLines","isCollectionsSelectToken","isSelectTypeToken","line","split","newLine","replace","_","variable","type","match","startsWith","_match","left","right","push","join"],"mappings":"AAAA,OAAO,MAAMA,oCAAoC,CAAC,EAChDC,sBAAsB,EAGvB;IACC,MAAMC,gBAA0B,EAAE;IAElC,IAAIC,2BAA2B;IAC/B,IAAIC,oBAAoB;IAExB,KAAK,MAAMC,QAAQJ,uBAAuBK,KAAK,CAAC,MAAO;QACrD,IAAIC,UAAUF;QACd,IAAIA,SAAS,CAAC,sBAAsB,CAAC,IAAIA,SAAS,CAAC,kBAAkB,CAAC,EAAE;YACtEF,2BAA2B;QAC7B;QAEA,IAAIA,0BAA0B;YAC5B,IAAIE,SAAS,QAAQ;gBACnBF,2BAA2B;YAC7B,OAAO;gBACL,qFAAqF;gBACrFI,UAAUF,KAAKG,OAAO,CAAC,8BAA8B,CAACC,GAAGC,UAAUC;oBACjE,OAAO,GAAGD,SAAS,EAAE,EAAEC,KAAK,UAAU,EAAEA,KAAK,OAAO,CAAC;gBACvD;YACF;QACF;QAEA,IAAIN,KAAKO,KAAK,CAAC,4CAA4C;YACzDR,oBAAoB;QACtB;QAEA,IAAIA,mBAAmB;YACrB,IAAIC,KAAKQ,UAAU,CAAC,qBAAqB;gBACvC,+BAA+B;gBAC/BN,UAAUF,KAAKG,OAAO,CAAC,oCAAoC;YAC7D,OAAO;gBACLD,UAAUF,IACR,sCAAsC;iBACrCG,OAAO,CAAC,uBAAuB,IAChC,uDAAuD;iBACtDA,OAAO,CACN,2BACA,CAACM,QAAQC,MAAMC,QAAU,GAAGD,KAAK,GAAG,EAAEC,MAAM,CAAC,EAAED,KAAK,CAAC,CAAC;gBAG1D,IAAIV,SAAS,KAAK;oBAChBD,oBAAoB;gBACtB;YACF;QACF;QAEAF,cAAce,IAAI,CAACV;IACrB;IAEA,OAAOL,cAAcgB,IAAI,CAAC;AAC5B,EAAC"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utilities/appendDateTimezoneSelectFields.ts"],"sourcesContent":["import type { FlattenedField } from '../fields/config/types.js'\nimport type { SelectType } from '../types/index.js'\n\n/**\n * Mutates the incoming select object to append `{name}_tz` siblings for any date\n * field with `timezone` enabled that has been requested in the select. Without\n * this, the List View's Select API would omit the timezone sibling and\n * cells would render dates in the server's timezone.\n */\nexport const appendDateTimezoneSelectFields = ({\n fields,\n select,\n}: {\n fields: FlattenedField[]\n select: SelectType\n}) => {\n if (!select) {\n return\n }\n\n for (const field of fields) {\n if (!('name' in field)) {\n continue\n }\n\n const value = select[field.name]\n\n if (field.type === 'date' && field.timezone && value) {\n select[`${field.name}_tz`] = true\n continue\n }\n\n if (!value || typeof value !== 'object') {\n continue\n }\n\n if ('flattenedFields' in field) {\n appendDateTimezoneSelectFields({\n fields: field.flattenedFields,\n select: value as SelectType,\n })\n continue\n }\n\n if (field.type === 'blocks') {\n const blocks = field.blocks as Array<\n | {\n flattenedFields: FlattenedField[]\n slug: string\n }\n | string\n >\n\n for (const block of blocks) {\n if (typeof block === 'string') {\n continue\n }\n const blockSelect = (value as SelectType)[block.slug]\n if (blockSelect && typeof blockSelect === 'object') {\n appendDateTimezoneSelectFields({\n fields: block.flattenedFields,\n select: blockSelect as SelectType,\n })\n }\n }\n }\n }\n}\n"],"names":["appendDateTimezoneSelectFields","fields","select","field","value","name","type","timezone","flattenedFields","blocks","block","blockSelect","slug"],"mappings":"AAGA;;;;;CAKC,GACD,OAAO,MAAMA,iCAAiC,CAAC,EAC7CC,MAAM,EACNC,MAAM,EAIP;IACC,IAAI,CAACA,QAAQ;QACX;IACF;IAEA,KAAK,MAAMC,SAASF,OAAQ;QAC1B,IAAI,CAAE,CAAA,UAAUE,KAAI,GAAI;YACtB;QACF;QAEA,MAAMC,QAAQF,MAAM,CAACC,MAAME,IAAI,CAAC;QAEhC,IAAIF,MAAMG,IAAI,KAAK,UAAUH,MAAMI,QAAQ,IAAIH,OAAO;YACpDF,MAAM,CAAC,GAAGC,MAAME,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG;YAC7B;QACF;QAEA,IAAI,CAACD,SAAS,OAAOA,UAAU,UAAU;YACvC;QACF;QAEA,IAAI,qBAAqBD,OAAO;YAC9BH,+BAA+B;gBAC7BC,QAAQE,MAAMK,eAAe;gBAC7BN,QAAQE;YACV;YACA;QACF;QAEA,IAAID,MAAMG,IAAI,KAAK,UAAU;YAC3B,MAAMG,SAASN,MAAMM,MAAM;YAQ3B,KAAK,MAAMC,SAASD,OAAQ;gBAC1B,IAAI,OAAOC,UAAU,UAAU;oBAC7B;gBACF;gBACA,MAAMC,cAAc,AAACP,KAAoB,CAACM,MAAME,IAAI,CAAC;gBACrD,IAAID,eAAe,OAAOA,gBAAgB,UAAU;oBAClDX,+BAA+B;wBAC7BC,QAAQS,MAAMF,eAAe;wBAC7BN,QAAQS;oBACV;gBACF;YACF;QACF;IACF;AACF,EAAC"}
1
+ {"version":3,"sources":["../../src/utilities/appendDateTimezoneSelectFields.ts"],"sourcesContent":["import type { FlattenedField } from '../fields/config/types.js'\nimport type { SelectType } from '../types/index.js'\n\n/**\n * Mutates the incoming select object to append `{name}_tz` siblings for any date\n * field with `timezone` enabled that has been requested in the select. Without\n * this, the List View's Select API would omit the timezone sibling and\n * cells would render dates in the server's timezone.\n */\nexport const appendDateTimezoneSelectFields = ({\n fields,\n select,\n}: {\n fields: FlattenedField[]\n select: SelectType\n}) => {\n if (!select) {\n return\n }\n\n for (const field of fields) {\n if (!('name' in field)) {\n continue\n }\n\n const value = select[field.name]\n\n if (field.type === 'date' && field.timezone && value) {\n select[`${field.name}_tz`] = true\n continue\n }\n\n if (!value || typeof value !== 'object') {\n continue\n }\n\n if ('flattenedFields' in field) {\n appendDateTimezoneSelectFields({\n fields: field.flattenedFields,\n select: value,\n })\n continue\n }\n\n if (field.type === 'blocks') {\n const blocks = field.blocks as Array<\n | {\n flattenedFields: FlattenedField[]\n slug: string\n }\n | string\n >\n\n for (const block of blocks) {\n if (typeof block === 'string') {\n continue\n }\n const blockSelect = value[block.slug]\n if (blockSelect && typeof blockSelect === 'object') {\n appendDateTimezoneSelectFields({\n fields: block.flattenedFields,\n select: blockSelect,\n })\n }\n }\n }\n }\n}\n"],"names":["appendDateTimezoneSelectFields","fields","select","field","value","name","type","timezone","flattenedFields","blocks","block","blockSelect","slug"],"mappings":"AAGA;;;;;CAKC,GACD,OAAO,MAAMA,iCAAiC,CAAC,EAC7CC,MAAM,EACNC,MAAM,EAIP;IACC,IAAI,CAACA,QAAQ;QACX;IACF;IAEA,KAAK,MAAMC,SAASF,OAAQ;QAC1B,IAAI,CAAE,CAAA,UAAUE,KAAI,GAAI;YACtB;QACF;QAEA,MAAMC,QAAQF,MAAM,CAACC,MAAME,IAAI,CAAC;QAEhC,IAAIF,MAAMG,IAAI,KAAK,UAAUH,MAAMI,QAAQ,IAAIH,OAAO;YACpDF,MAAM,CAAC,GAAGC,MAAME,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG;YAC7B;QACF;QAEA,IAAI,CAACD,SAAS,OAAOA,UAAU,UAAU;YACvC;QACF;QAEA,IAAI,qBAAqBD,OAAO;YAC9BH,+BAA+B;gBAC7BC,QAAQE,MAAMK,eAAe;gBAC7BN,QAAQE;YACV;YACA;QACF;QAEA,IAAID,MAAMG,IAAI,KAAK,UAAU;YAC3B,MAAMG,SAASN,MAAMM,MAAM;YAQ3B,KAAK,MAAMC,SAASD,OAAQ;gBAC1B,IAAI,OAAOC,UAAU,UAAU;oBAC7B;gBACF;gBACA,MAAMC,cAAcP,KAAK,CAACM,MAAME,IAAI,CAAC;gBACrC,IAAID,eAAe,OAAOA,gBAAgB,UAAU;oBAClDX,+BAA+B;wBAC7BC,QAAQS,MAAMF,eAAe;wBAC7BN,QAAQS;oBACV;gBACF;YACF;QACF;IACF;AACF,EAAC"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utilities/cloneValidationRequest.ts"],"sourcesContent":["import type { RequestContext, User } from '../index.js'\nimport type { PayloadRequest } from '../types/index.js'\n\nconst sharedValidationRequestProperties = new Set([\n 'i18n',\n 'payload',\n 'server',\n 'signal',\n 't',\n 'transactionID',\n])\n\nexport function cloneValidationContext({\n context,\n}: {\n context: RequestContext | undefined\n}): RequestContext | undefined {\n return cloneValidationValue(context)\n}\n\nexport function cloneValidationData<TData>({ data }: { data: TData }): TData {\n return cloneValidationValue(data)\n}\n\nexport function cloneValidationRequest({\n request,\n}: {\n request: Partial<PayloadRequest> | undefined\n}): Partial<PayloadRequest> {\n if (!request) {\n return {}\n }\n\n const payloadRequest: Partial<PayloadRequest> = request\n const fetchRequest: Request | undefined =\n typeof Request !== 'undefined' && request instanceof Request ? request : undefined\n const canCloneFetchRequest = fetchRequest && !fetchRequest.bodyUsed\n let clonedRequest: Record<string, unknown>\n\n if (canCloneFetchRequest) {\n clonedRequest = fetchRequest.clone() as unknown as Record<string, unknown>\n } else {\n clonedRequest = {}\n }\n\n for (const [key, value] of Object.entries(payloadRequest)) {\n if (key === 'payloadDataLoader') {\n continue\n }\n\n if (key === 'file') {\n clonedRequest.file = cloneValidationFile(payloadRequest.file)\n continue\n }\n\n if (key === 'files') {\n clonedRequest.files = cloneValidationFiles(payloadRequest.files)\n continue\n }\n\n clonedRequest[key] = sharedValidationRequestProperties.has(key)\n ? value\n : cloneValidationValue(value)\n }\n\n Object.assign(clonedRequest, {\n context: cloneValidationValue(payloadRequest.context ?? {}),\n query: cloneValidationValue(payloadRequest.query ?? {}),\n routeParams: cloneValidationValue(payloadRequest.routeParams ?? {}),\n })\n\n if (!canCloneFetchRequest) {\n Object.assign(clonedRequest, {\n headers: cloneValidationValue(payloadRequest.headers),\n method: payloadRequest.method,\n signal: payloadRequest.signal,\n url: payloadRequest.url,\n })\n }\n\n return clonedRequest as Partial<PayloadRequest>\n}\n\nexport function cloneValidationUser({\n user,\n}: {\n user: null | undefined | User\n}): null | undefined | User {\n return cloneValidationValue(user)\n}\n\nfunction cloneValidationFile(file: PayloadRequest['file']): PayloadRequest['file'] {\n if (!file) {\n return file\n }\n\n const { data, ...metadata } = file\n\n return {\n ...cloneValidationValue(metadata),\n data,\n }\n}\n\nfunction cloneValidationFiles(files: PayloadRequest['files']): PayloadRequest['files'] {\n if (!files) {\n return files\n }\n\n return Object.fromEntries(\n Object.entries(files).map(([fieldName, fileOrFiles]) => [\n fieldName,\n Array.isArray(fileOrFiles)\n ? fileOrFiles.map((file) => cloneValidationFile(file)!)\n : cloneValidationFile(fileOrFiles)!,\n ]),\n )\n}\n\nfunction cloneValidationValue<T>(value: T, cache = new WeakMap<object, unknown>()): T {\n if ((typeof value !== 'object' && typeof value !== 'function') || value === null) {\n return value\n }\n\n if (typeof value === 'function' || value instanceof Promise) {\n return value\n }\n\n const objectValue = value as object\n const cachedValue = cache.get(objectValue)\n\n if (cachedValue) {\n return cachedValue as T\n }\n\n if (value instanceof Headers) {\n return new Headers(value) as T\n }\n\n if (value instanceof URLSearchParams) {\n return new URLSearchParams(value) as T\n }\n\n if (value instanceof URL) {\n return new URL(value) as T\n }\n\n if (value instanceof Date) {\n return new Date(value) as T\n }\n\n if (value instanceof RegExp) {\n return new RegExp(value.source, value.flags) as T\n }\n\n if (value instanceof ArrayBuffer) {\n return value.slice(0) as T\n }\n\n if (ArrayBuffer.isView(value)) {\n if (Buffer.isBuffer(value)) {\n return Buffer.from(value) as T\n }\n\n if (value instanceof DataView) {\n return new DataView(value.buffer.slice(0), value.byteOffset, value.byteLength) as T\n }\n\n return new (value.constructor as new (input: typeof value) => typeof value)(value)\n }\n\n if (value instanceof Map) {\n const clonedMap = new Map()\n cache.set(objectValue, clonedMap)\n for (const [key, mapValue] of value) {\n clonedMap.set(cloneValidationValue(key, cache), cloneValidationValue(mapValue, cache))\n }\n return clonedMap as T\n }\n\n if (value instanceof Set) {\n const clonedSet = new Set()\n cache.set(objectValue, clonedSet)\n for (const setValue of value) {\n clonedSet.add(cloneValidationValue(setValue, cache))\n }\n return clonedSet as T\n }\n\n if (typeof Blob !== 'undefined' && value instanceof Blob) {\n return value\n }\n\n const prototype = Object.getPrototypeOf(value)\n\n if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) {\n return value\n }\n\n const clonedValue: Record<PropertyKey, unknown> | unknown[] = Array.isArray(value)\n ? []\n : Object.create(prototype)\n cache.set(objectValue, clonedValue)\n\n for (const key of Reflect.ownKeys(value)) {\n const descriptor = Object.getOwnPropertyDescriptor(value, key)\n\n if (descriptor?.enumerable) {\n ;(clonedValue as Record<PropertyKey, unknown>)[key] = cloneValidationValue(\n (value as Record<PropertyKey, unknown>)[key],\n cache,\n )\n }\n }\n\n return clonedValue as T\n}\n"],"names":["sharedValidationRequestProperties","Set","cloneValidationContext","context","cloneValidationValue","cloneValidationData","data","cloneValidationRequest","request","payloadRequest","fetchRequest","Request","undefined","canCloneFetchRequest","bodyUsed","clonedRequest","clone","key","value","Object","entries","file","cloneValidationFile","files","cloneValidationFiles","has","assign","query","routeParams","headers","method","signal","url","cloneValidationUser","user","metadata","fromEntries","map","fieldName","fileOrFiles","Array","isArray","cache","WeakMap","Promise","objectValue","cachedValue","get","Headers","URLSearchParams","URL","Date","RegExp","source","flags","ArrayBuffer","slice","isView","Buffer","isBuffer","from","DataView","buffer","byteOffset","byteLength","Map","clonedMap","set","mapValue","clonedSet","setValue","add","Blob","prototype","getPrototypeOf","clonedValue","create","Reflect","ownKeys","descriptor","getOwnPropertyDescriptor","enumerable"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/utilities/cloneValidationRequest.ts"],"sourcesContent":["import type { RequestContext, User } from '../index.js'\nimport type { PayloadRequest } from '../types/index.js'\n\nconst sharedValidationRequestProperties = new Set([\n 'i18n',\n 'payload',\n 'server',\n 'signal',\n 't',\n 'transactionID',\n])\n\nexport function cloneValidationContext({\n context,\n}: {\n context: RequestContext | undefined\n}): RequestContext | undefined {\n return cloneValidationValue(context)\n}\n\nexport function cloneValidationData<TData>({ data }: { data: TData }): TData {\n return cloneValidationValue(data)\n}\n\nexport function cloneValidationRequest({\n request,\n}: {\n request: Partial<PayloadRequest> | undefined\n}): Partial<PayloadRequest> {\n if (!request) {\n return {}\n }\n\n const payloadRequest: Partial<PayloadRequest> = request\n const fetchRequest: Request | undefined =\n typeof Request !== 'undefined' && request instanceof Request ? request : undefined\n const canCloneFetchRequest = fetchRequest && !fetchRequest.bodyUsed\n let clonedRequest: Record<string, unknown>\n\n if (canCloneFetchRequest) {\n clonedRequest = fetchRequest.clone() as unknown as Record<string, unknown>\n } else {\n clonedRequest = {}\n }\n\n for (const [key, value] of Object.entries(payloadRequest)) {\n if (key === 'payloadDataLoader') {\n continue\n }\n\n if (key === 'file') {\n clonedRequest.file = cloneValidationFile(payloadRequest.file)\n continue\n }\n\n if (key === 'files') {\n clonedRequest.files = cloneValidationFiles(payloadRequest.files)\n continue\n }\n\n clonedRequest[key] = sharedValidationRequestProperties.has(key)\n ? value\n : cloneValidationValue(value)\n }\n\n Object.assign(clonedRequest, {\n context: cloneValidationValue(payloadRequest.context ?? {}),\n query: cloneValidationValue(payloadRequest.query ?? {}),\n routeParams: cloneValidationValue(payloadRequest.routeParams ?? {}),\n })\n\n if (!canCloneFetchRequest) {\n Object.assign(clonedRequest, {\n headers: cloneValidationValue(payloadRequest.headers),\n method: payloadRequest.method,\n signal: payloadRequest.signal,\n url: payloadRequest.url,\n })\n }\n\n return clonedRequest\n}\n\nexport function cloneValidationUser({\n user,\n}: {\n user: null | undefined | User\n}): null | undefined | User {\n return cloneValidationValue(user)\n}\n\nfunction cloneValidationFile(file: PayloadRequest['file']): PayloadRequest['file'] {\n if (!file) {\n return file\n }\n\n const { data, ...metadata } = file\n\n return {\n ...cloneValidationValue(metadata),\n data,\n }\n}\n\nfunction cloneValidationFiles(files: PayloadRequest['files']): PayloadRequest['files'] {\n if (!files) {\n return files\n }\n\n return Object.fromEntries(\n Object.entries(files).map(([fieldName, fileOrFiles]) => [\n fieldName,\n Array.isArray(fileOrFiles)\n ? fileOrFiles.map((file) => cloneValidationFile(file)!)\n : cloneValidationFile(fileOrFiles)!,\n ]),\n )\n}\n\nfunction cloneValidationValue<T>(value: T, cache = new WeakMap<object, unknown>()): T {\n if ((typeof value !== 'object' && typeof value !== 'function') || value === null) {\n return value\n }\n\n if (typeof value === 'function' || value instanceof Promise) {\n return value\n }\n\n const objectValue = value as object\n const cachedValue = cache.get(objectValue)\n\n if (cachedValue) {\n return cachedValue as T\n }\n\n if (value instanceof Headers) {\n return new Headers(value) as T\n }\n\n if (value instanceof URLSearchParams) {\n return new URLSearchParams(value) as T\n }\n\n if (value instanceof URL) {\n return new URL(value) as T\n }\n\n if (value instanceof Date) {\n return new Date(value) as T\n }\n\n if (value instanceof RegExp) {\n return new RegExp(value.source, value.flags) as T\n }\n\n if (value instanceof ArrayBuffer) {\n return value.slice(0) as T\n }\n\n if (ArrayBuffer.isView(value)) {\n if (Buffer.isBuffer(value)) {\n return Buffer.from(value) as T\n }\n\n if (value instanceof DataView) {\n return new DataView(value.buffer.slice(0), value.byteOffset, value.byteLength) as T\n }\n\n return new (value.constructor as new (input: typeof value) => typeof value)(value)\n }\n\n if (value instanceof Map) {\n const clonedMap = new Map()\n cache.set(objectValue, clonedMap)\n for (const [key, mapValue] of value) {\n clonedMap.set(cloneValidationValue(key, cache), cloneValidationValue(mapValue, cache))\n }\n return clonedMap as T\n }\n\n if (value instanceof Set) {\n const clonedSet = new Set()\n cache.set(objectValue, clonedSet)\n for (const setValue of value) {\n clonedSet.add(cloneValidationValue(setValue, cache))\n }\n return clonedSet as T\n }\n\n if (typeof Blob !== 'undefined' && value instanceof Blob) {\n return value\n }\n\n const prototype = Object.getPrototypeOf(value)\n\n if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) {\n return value\n }\n\n const clonedValue: Record<PropertyKey, unknown> | unknown[] = Array.isArray(value)\n ? []\n : Object.create(prototype)\n cache.set(objectValue, clonedValue)\n\n for (const key of Reflect.ownKeys(value)) {\n const descriptor = Object.getOwnPropertyDescriptor(value, key)\n\n if (descriptor?.enumerable) {\n ;(clonedValue as Record<PropertyKey, unknown>)[key] = cloneValidationValue(\n (value as Record<PropertyKey, unknown>)[key],\n cache,\n )\n }\n }\n\n return clonedValue as T\n}\n"],"names":["sharedValidationRequestProperties","Set","cloneValidationContext","context","cloneValidationValue","cloneValidationData","data","cloneValidationRequest","request","payloadRequest","fetchRequest","Request","undefined","canCloneFetchRequest","bodyUsed","clonedRequest","clone","key","value","Object","entries","file","cloneValidationFile","files","cloneValidationFiles","has","assign","query","routeParams","headers","method","signal","url","cloneValidationUser","user","metadata","fromEntries","map","fieldName","fileOrFiles","Array","isArray","cache","WeakMap","Promise","objectValue","cachedValue","get","Headers","URLSearchParams","URL","Date","RegExp","source","flags","ArrayBuffer","slice","isView","Buffer","isBuffer","from","DataView","buffer","byteOffset","byteLength","Map","clonedMap","set","mapValue","clonedSet","setValue","add","Blob","prototype","getPrototypeOf","clonedValue","create","Reflect","ownKeys","descriptor","getOwnPropertyDescriptor","enumerable"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utilities/createPayloadRequest.ts"],"sourcesContent":["import type { Payload, RequestContext, TypedLocale, User } from '../index.js'\nimport type { PayloadRequest } from '../types/index.js'\n\nimport { getDataLoader } from '../collections/dataloader.js'\nimport { getLocalI18n } from '../translations/getLocalI18n.js'\nimport { sanitizeFallbackLocale } from '../utilities/sanitizeFallbackLocale.js'\n\nfunction getRequestContext(\n req: Partial<PayloadRequest> = { context: null } as unknown as PayloadRequest,\n context: RequestContext = {},\n): RequestContext {\n if (req.context) {\n if (Object.keys(req.context).length === 0 && req.context.constructor === Object) {\n // if req.context is `{}` avoid unnecessary spread\n return context\n } else {\n return { ...req.context, ...context }\n }\n } else {\n return context\n }\n}\n\nconst attachFakeURLProperties = (req: Partial<PayloadRequest>, urlSuffix?: string) => {\n /**\n * *NOTE*\n * If no URL is provided, the local API was called outside\n * the context of a request. Therefore we create a fake URL object.\n * `ts-expect-error` is used below for properties that are 'read-only'.\n * Since they do not exist yet we can safely ignore the error.\n */\n let urlObject: undefined | URL\n\n function getURLObject() {\n if (urlObject) {\n return urlObject\n }\n\n const fallbackURL = `http://${req.host || 'localhost'}${urlSuffix || ''}`\n\n const urlToUse =\n req?.url ||\n (req.payload?.config?.serverURL\n ? `${req.payload?.config.serverURL}${urlSuffix || ''}`\n : fallbackURL)\n\n try {\n urlObject = new URL(urlToUse)\n } catch (_err) {\n req.payload?.logger.error(\n `Failed to create URL object from URL: ${urlToUse}, falling back to ${fallbackURL}`,\n )\n\n urlObject = new URL(fallbackURL)\n }\n\n return urlObject\n }\n\n if (!req.host) {\n req.host = getURLObject().host\n }\n\n if (!req.protocol) {\n req.protocol = getURLObject().protocol\n }\n\n if (!req.pathname) {\n req.pathname = getURLObject().pathname\n }\n\n if (!req.searchParams) {\n // @ts-expect-error eslint-disable-next-line no-param-reassign\n req.searchParams = getURLObject().searchParams\n }\n\n if (!req.origin) {\n // @ts-expect-error eslint-disable-next-line no-param-reassign\n req.origin = getURLObject().origin\n }\n\n if (!req?.url) {\n // @ts-expect-error eslint-disable-next-line no-param-reassign\n req.url = getURLObject().href\n }\n}\n\nexport type CreatePayloadRequestArgs = {\n context?: RequestContext\n depth?: number\n fallbackLocale?: false | TypedLocale\n locale?: string\n payload: Payload\n req?: Partial<PayloadRequest>\n urlSuffix?: string\n user?: null | User\n}\n\ntype CreatePayloadRequest = (args: CreatePayloadRequestArgs) => Promise<PayloadRequest>\n\nexport const createPayloadRequest: CreatePayloadRequest = async ({\n context,\n depth,\n fallbackLocale,\n locale: localeArg,\n payload,\n req = {} as PayloadRequest,\n urlSuffix,\n user,\n}): Promise<PayloadRequest> => {\n const localization = payload.config?.localization\n\n if (localization) {\n const locale = localeArg === '*' ? 'all' : localeArg\n const defaultLocale = localization.defaultLocale\n const localeCandidate = locale || req?.locale || req?.query?.locale\n\n req.locale =\n localeCandidate && typeof localeCandidate === 'string' ? localeCandidate : defaultLocale\n\n const sanitizedFallback = sanitizeFallbackLocale({\n fallbackLocale: fallbackLocale!,\n locale: req.locale,\n localization,\n })\n\n req.fallbackLocale = sanitizedFallback!\n }\n\n const i18n =\n req?.i18n ||\n (await getLocalI18n({ config: payload.config, language: payload.config.i18n.fallbackLanguage }))\n\n if (!req.headers) {\n req.headers = new Headers()\n }\n\n req.context = getRequestContext(req, context)\n req.payloadAPI = req?.payloadAPI || 'local'\n req.payload = payload\n req.i18n = i18n\n req.t = i18n.t\n req.user = user === undefined ? (req?.user ?? null) : user\n\n // Ensure user.collection is set for auth-related access control\n // TODO (4.0): Instead of silently falling back, throw an error if user.collection is missing\n if (req.user && !req.user.collection) {\n req.user = { ...req.user, collection: payload.config.admin.user }\n }\n\n req.payloadDataLoader = req?.payloadDataLoader || getDataLoader(req as PayloadRequest)\n req.routeParams = req?.routeParams || {}\n req.query = req?.query || {}\n\n if (typeof depth !== 'undefined') {\n req.query.depth = depth\n }\n\n attachFakeURLProperties(req, urlSuffix)\n\n return req as PayloadRequest\n}\n"],"names":["getDataLoader","getLocalI18n","sanitizeFallbackLocale","getRequestContext","req","context","Object","keys","length","attachFakeURLProperties","urlSuffix","urlObject","getURLObject","fallbackURL","host","urlToUse","url","payload","config","serverURL","URL","_err","logger","error","protocol","pathname","searchParams","origin","href","createPayloadRequest","depth","fallbackLocale","locale","localeArg","user","localization","defaultLocale","localeCandidate","query","sanitizedFallback","i18n","language","fallbackLanguage","headers","Headers","payloadAPI","t","undefined","collection","admin","payloadDataLoader","routeParams"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/utilities/createPayloadRequest.ts"],"sourcesContent":["import type { Payload, RequestContext, TypedLocale, User } from '../index.js'\nimport type { PayloadRequest } from '../types/index.js'\n\nimport { getDataLoader } from '../collections/dataloader.js'\nimport { getLocalI18n } from '../translations/getLocalI18n.js'\nimport { sanitizeFallbackLocale } from '../utilities/sanitizeFallbackLocale.js'\n\nfunction getRequestContext(\n req: Partial<PayloadRequest> = { context: null } as unknown as PayloadRequest,\n context: RequestContext = {},\n): RequestContext {\n if (req.context) {\n if (Object.keys(req.context).length === 0 && req.context.constructor === Object) {\n // if req.context is `{}` avoid unnecessary spread\n return context\n } else {\n return { ...req.context, ...context }\n }\n } else {\n return context\n }\n}\n\nconst attachFakeURLProperties = (req: Partial<PayloadRequest>, urlSuffix?: string) => {\n /**\n * *NOTE*\n * If no URL is provided, the local API was called outside\n * the context of a request. Therefore we create a fake URL object.\n * `ts-expect-error` is used below for properties that are 'read-only'.\n * Since they do not exist yet we can safely ignore the error.\n */\n let urlObject: undefined | URL\n\n function getURLObject() {\n if (urlObject) {\n return urlObject\n }\n\n const fallbackURL = `http://${req.host || 'localhost'}${urlSuffix || ''}`\n\n const urlToUse =\n req?.url ||\n (req.payload?.config?.serverURL\n ? `${req.payload?.config.serverURL}${urlSuffix || ''}`\n : fallbackURL)\n\n try {\n urlObject = new URL(urlToUse)\n } catch (_err) {\n req.payload?.logger.error(\n `Failed to create URL object from URL: ${urlToUse}, falling back to ${fallbackURL}`,\n )\n\n urlObject = new URL(fallbackURL)\n }\n\n return urlObject\n }\n\n if (!req.host) {\n req.host = getURLObject().host\n }\n\n if (!req.protocol) {\n req.protocol = getURLObject().protocol\n }\n\n if (!req.pathname) {\n req.pathname = getURLObject().pathname\n }\n\n if (!req.searchParams) {\n // @ts-expect-error eslint-disable-next-line no-param-reassign\n req.searchParams = getURLObject().searchParams\n }\n\n if (!req.origin) {\n // @ts-expect-error eslint-disable-next-line no-param-reassign\n req.origin = getURLObject().origin\n }\n\n if (!req?.url) {\n // @ts-expect-error eslint-disable-next-line no-param-reassign\n req.url = getURLObject().href\n }\n}\n\nexport type CreatePayloadRequestArgs = {\n context?: RequestContext\n depth?: number\n fallbackLocale?: false | TypedLocale\n locale?: string\n payload: Payload\n req?: Partial<PayloadRequest>\n urlSuffix?: string\n user?: null | User\n}\n\ntype CreatePayloadRequest = (args: CreatePayloadRequestArgs) => Promise<PayloadRequest>\n\nexport const createPayloadRequest: CreatePayloadRequest = async ({\n context,\n depth,\n fallbackLocale,\n locale: localeArg,\n payload,\n req = {},\n urlSuffix,\n user,\n}): Promise<PayloadRequest> => {\n const localization = payload.config?.localization\n\n if (localization) {\n const locale = localeArg === '*' ? 'all' : localeArg\n const defaultLocale = localization.defaultLocale\n const localeCandidate = locale || req?.locale || req?.query?.locale\n\n req.locale =\n localeCandidate && typeof localeCandidate === 'string' ? localeCandidate : defaultLocale\n\n const sanitizedFallback = sanitizeFallbackLocale({\n fallbackLocale: fallbackLocale!,\n locale: req.locale,\n localization,\n })\n\n req.fallbackLocale = sanitizedFallback!\n }\n\n const i18n =\n req?.i18n ||\n (await getLocalI18n({ config: payload.config, language: payload.config.i18n.fallbackLanguage }))\n\n if (!req.headers) {\n req.headers = new Headers()\n }\n\n req.context = getRequestContext(req, context)\n req.payloadAPI = req?.payloadAPI || 'local'\n req.payload = payload\n req.i18n = i18n\n req.t = i18n.t\n req.user = user === undefined ? (req?.user ?? null) : user\n\n // Ensure user.collection is set for auth-related access control\n // TODO (4.0): Instead of silently falling back, throw an error if user.collection is missing\n if (req.user && !req.user.collection) {\n req.user = { ...req.user, collection: payload.config.admin.user }\n }\n\n req.payloadDataLoader = req?.payloadDataLoader || getDataLoader(req as PayloadRequest)\n req.routeParams = req?.routeParams || {}\n req.query = req?.query || {}\n\n if (typeof depth !== 'undefined') {\n req.query.depth = depth\n }\n\n attachFakeURLProperties(req, urlSuffix)\n\n return req as PayloadRequest\n}\n"],"names":["getDataLoader","getLocalI18n","sanitizeFallbackLocale","getRequestContext","req","context","Object","keys","length","attachFakeURLProperties","urlSuffix","urlObject","getURLObject","fallbackURL","host","urlToUse","url","payload","config","serverURL","URL","_err","logger","error","protocol","pathname","searchParams","origin","href","createPayloadRequest","depth","fallbackLocale","locale","localeArg","user","localization","defaultLocale","localeCandidate","query","sanitizedFallback","i18n","language","fallbackLanguage","headers","Headers","payloadAPI","t","undefined","collection","admin","payloadDataLoader","routeParams"],"mappings":"AAGA,SAASA,aAAa,QAAQ,+BAA8B;AAC5D,SAASC,YAAY,QAAQ,kCAAiC;AAC9D,SAASC,sBAAsB,QAAQ,yCAAwC;AAE/E,SAASC,kBACPC,MAA+B;IAAEC,SAAS;AAAK,CAA8B,EAC7EA,UAA0B,CAAC,CAAC;IAE5B,IAAID,IAAIC,OAAO,EAAE;QACf,IAAIC,OAAOC,IAAI,CAACH,IAAIC,OAAO,EAAEG,MAAM,KAAK,KAAKJ,IAAIC,OAAO,CAAC,WAAW,KAAKC,QAAQ;YAC/E,kDAAkD;YAClD,OAAOD;QACT,OAAO;YACL,OAAO;gBAAE,GAAGD,IAAIC,OAAO;gBAAE,GAAGA,OAAO;YAAC;QACtC;IACF,OAAO;QACL,OAAOA;IACT;AACF;AAEA,MAAMI,0BAA0B,CAACL,KAA8BM;IAC7D;;;;;;GAMC,GACD,IAAIC;IAEJ,SAASC;QACP,IAAID,WAAW;YACb,OAAOA;QACT;QAEA,MAAME,cAAc,CAAC,OAAO,EAAET,IAAIU,IAAI,IAAI,cAAcJ,aAAa,IAAI;QAEzE,MAAMK,WACJX,KAAKY,OACJZ,CAAAA,IAAIa,OAAO,EAAEC,QAAQC,YAClB,GAAGf,IAAIa,OAAO,EAAEC,OAAOC,YAAYT,aAAa,IAAI,GACpDG,WAAU;QAEhB,IAAI;YACFF,YAAY,IAAIS,IAAIL;QACtB,EAAE,OAAOM,MAAM;YACbjB,IAAIa,OAAO,EAAEK,OAAOC,MAClB,CAAC,sCAAsC,EAAER,SAAS,kBAAkB,EAAEF,aAAa;YAGrFF,YAAY,IAAIS,IAAIP;QACtB;QAEA,OAAOF;IACT;IAEA,IAAI,CAACP,IAAIU,IAAI,EAAE;QACbV,IAAIU,IAAI,GAAGF,eAAeE,IAAI;IAChC;IAEA,IAAI,CAACV,IAAIoB,QAAQ,EAAE;QACjBpB,IAAIoB,QAAQ,GAAGZ,eAAeY,QAAQ;IACxC;IAEA,IAAI,CAACpB,IAAIqB,QAAQ,EAAE;QACjBrB,IAAIqB,QAAQ,GAAGb,eAAea,QAAQ;IACxC;IAEA,IAAI,CAACrB,IAAIsB,YAAY,EAAE;QACrB,8DAA8D;QAC9DtB,IAAIsB,YAAY,GAAGd,eAAec,YAAY;IAChD;IAEA,IAAI,CAACtB,IAAIuB,MAAM,EAAE;QACf,8DAA8D;QAC9DvB,IAAIuB,MAAM,GAAGf,eAAee,MAAM;IACpC;IAEA,IAAI,CAACvB,KAAKY,KAAK;QACb,8DAA8D;QAC9DZ,IAAIY,GAAG,GAAGJ,eAAegB,IAAI;IAC/B;AACF;AAeA,OAAO,MAAMC,uBAA6C,OAAO,EAC/DxB,OAAO,EACPyB,KAAK,EACLC,cAAc,EACdC,QAAQC,SAAS,EACjBhB,OAAO,EACPb,MAAM,CAAC,CAAC,EACRM,SAAS,EACTwB,IAAI,EACL;IACC,MAAMC,eAAelB,QAAQC,MAAM,EAAEiB;IAErC,IAAIA,cAAc;QAChB,MAAMH,SAASC,cAAc,MAAM,QAAQA;QAC3C,MAAMG,gBAAgBD,aAAaC,aAAa;QAChD,MAAMC,kBAAkBL,UAAU5B,KAAK4B,UAAU5B,KAAKkC,OAAON;QAE7D5B,IAAI4B,MAAM,GACRK,mBAAmB,OAAOA,oBAAoB,WAAWA,kBAAkBD;QAE7E,MAAMG,oBAAoBrC,uBAAuB;YAC/C6B,gBAAgBA;YAChBC,QAAQ5B,IAAI4B,MAAM;YAClBG;QACF;QAEA/B,IAAI2B,cAAc,GAAGQ;IACvB;IAEA,MAAMC,OACJpC,KAAKoC,QACJ,MAAMvC,aAAa;QAAEiB,QAAQD,QAAQC,MAAM;QAAEuB,UAAUxB,QAAQC,MAAM,CAACsB,IAAI,CAACE,gBAAgB;IAAC;IAE/F,IAAI,CAACtC,IAAIuC,OAAO,EAAE;QAChBvC,IAAIuC,OAAO,GAAG,IAAIC;IACpB;IAEAxC,IAAIC,OAAO,GAAGF,kBAAkBC,KAAKC;IACrCD,IAAIyC,UAAU,GAAGzC,KAAKyC,cAAc;IACpCzC,IAAIa,OAAO,GAAGA;IACdb,IAAIoC,IAAI,GAAGA;IACXpC,IAAI0C,CAAC,GAAGN,KAAKM,CAAC;IACd1C,IAAI8B,IAAI,GAAGA,SAASa,YAAa3C,KAAK8B,QAAQ,OAAQA;IAEtD,gEAAgE;IAChE,6FAA6F;IAC7F,IAAI9B,IAAI8B,IAAI,IAAI,CAAC9B,IAAI8B,IAAI,CAACc,UAAU,EAAE;QACpC5C,IAAI8B,IAAI,GAAG;YAAE,GAAG9B,IAAI8B,IAAI;YAAEc,YAAY/B,QAAQC,MAAM,CAAC+B,KAAK,CAACf,IAAI;QAAC;IAClE;IAEA9B,IAAI8C,iBAAiB,GAAG9C,KAAK8C,qBAAqBlD,cAAcI;IAChEA,IAAI+C,WAAW,GAAG/C,KAAK+C,eAAe,CAAC;IACvC/C,IAAIkC,KAAK,GAAGlC,KAAKkC,SAAS,CAAC;IAE3B,IAAI,OAAOR,UAAU,aAAa;QAChC1B,IAAIkC,KAAK,CAACR,KAAK,GAAGA;IACpB;IAEArB,wBAAwBL,KAAKM;IAE7B,OAAON;AACT,EAAC"}