@humaan/payload-storage-imagekit 2.5.0 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3,8 +3,12 @@ import { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage';
3
3
  import path from 'node:path';
4
4
  import { getImageKitUploadAuthPath, isImageKitClientUploadContext } from './clientUploads.js';
5
5
  import { createImageKitUploadAuthHandler } from './endpoints/createImageKitUploadAuthHandler.js';
6
- import { getAdminThumbnailURL, getPrivateAdminThumbnailURL, isVideoOrPdfMimeType } from './getAdminThumbnailURL.js';
6
+ import { getAdminThumbnailURL, getPrivateAdminThumbnailURL, isPdfMimeType, isVideoMimeType } from './getAdminThumbnailURL.js';
7
+ import { getImageKitThumbnailSrc, getVideoPosterSrc } from './imageTransforms.js';
7
8
  const DEFAULT_SIGNED_URL_EXPIRES_IN = 900;
9
+ // Payload's file-type detector samples this many bytes when validating binary uploads.
10
+ const FILE_TYPE_DETECTION_SIZE = 4100;
11
+ const FILE_TYPE_DETECTION_RANGE = `bytes=0-${FILE_TYPE_DETECTION_SIZE - 1}`;
8
12
  const PRIVATE_COLLECTION_DESCRIPTION = 'Files in this collection are uploaded to ImageKit as private files and delivered through short-lived signed URLs.';
9
13
  const isPrivateCollectionConfig = (value)=>value !== true && typeof value === 'object' && value?.private === true;
10
14
  /**
@@ -51,6 +55,7 @@ const getStoredImageKitURL = (doc)=>{
51
55
  const storedURL = doc?.imagekit?.url;
52
56
  return typeof storedURL === 'string' && storedURL.trim().length > 0 ? storedURL : undefined;
53
57
  };
58
+ const hasStoredImageKitURL = (doc)=>!!getStoredImageKitURL(doc);
54
59
  const findUploadDocumentByFilename = async ({ collection, filename, prefix, req })=>{
55
60
  const constraints = [
56
61
  {
@@ -91,22 +96,39 @@ const applyClientUploadContextToData = (data, clientUploadContext, buildThumbnai
91
96
  return data;
92
97
  };
93
98
  const imagekitAdapter = ({ apiRoute, client, clientUploads, privateSlugs, rootFolder, signedUrlExpiresIn, urlEndpoint })=>{
94
- const fetchUploadedFile = async (fileURL)=>{
95
- const response = await fetch(fileURL);
99
+ const fetchUploadedFile = async (fileURL, range)=>{
100
+ const response = range ? await fetch(fileURL, {
101
+ headers: {
102
+ Range: range
103
+ }
104
+ }) : await fetch(fileURL);
96
105
  if (!response.ok) {
97
106
  throw new Error(`imagekitStorage: failed to fetch uploaded file from ImageKit (${response.status}).`);
98
107
  }
99
- const fileBuffer = await response.arrayBuffer();
100
- const headers = new Headers();
101
- const contentType = response.headers.get('Content-Type');
102
- if (contentType) {
103
- headers.set('Content-Type', contentType);
108
+ return response;
109
+ };
110
+ const fetchUploadedFilePrefix = async ({ fileSize, fileURL, mimeType })=>{
111
+ const response = await fetchUploadedFile(fileURL, FILE_TYPE_DETECTION_RANGE);
112
+ const contentLength = Number(response.headers.get('Content-Length'));
113
+ const contentRange = response.headers.get('Content-Range');
114
+ const rangeMatch = contentRange?.match(/^bytes 0-(\d+)\/(\d+)$/);
115
+ const rangeEnd = rangeMatch ? Number(rangeMatch[1]) : Number.NaN;
116
+ const totalSize = rangeMatch ? Number(rangeMatch[2]) : Number.NaN;
117
+ const expectedLength = Math.min(fileSize, FILE_TYPE_DETECTION_SIZE);
118
+ if (response.status !== 206 || !rangeMatch || !Number.isSafeInteger(contentLength) || contentLength !== expectedLength || !Number.isSafeInteger(rangeEnd) || rangeEnd + 1 !== contentLength || totalSize !== fileSize) {
119
+ await response.body?.cancel();
120
+ throw new Error('imagekitStorage: ImageKit returned an invalid video range response.');
121
+ }
122
+ const filePrefix = await response.arrayBuffer();
123
+ if (filePrefix.byteLength !== contentLength) {
124
+ throw new Error('imagekitStorage: ImageKit returned an invalid video range body.');
104
125
  }
105
- headers.set('Content-Length', String(fileBuffer.byteLength));
106
- return new Response(fileBuffer, {
107
- headers,
108
- status: 200,
109
- statusText: response.statusText
126
+ return new Response(filePrefix, {
127
+ headers: {
128
+ 'Content-Length': String(filePrefix.byteLength),
129
+ 'Content-Type': mimeType
130
+ },
131
+ status: 200
110
132
  });
111
133
  };
112
134
  return ({ collection })=>{
@@ -134,6 +156,7 @@ const imagekitAdapter = ({ apiRoute, client, clientUploads, privateSlugs, rootFo
134
156
  name: 'imagekit',
135
157
  type: 'group',
136
158
  admin: {
159
+ condition: (data)=>hasStoredImageKitURL(data),
137
160
  disableListColumn: true,
138
161
  disableListFilter: true
139
162
  },
@@ -150,13 +173,17 @@ const imagekitAdapter = ({ apiRoute, client, clientUploads, privateSlugs, rootFo
150
173
  type: 'text',
151
174
  admin: {
152
175
  readOnly: true
153
- }
176
+ },
177
+ label: 'URL'
154
178
  },
155
179
  {
156
180
  name: 'isPrivate',
157
181
  type: 'checkbox',
158
182
  admin: {
159
- description: 'Whether this file is stored as a private file on ImageKit. Set at upload time from the collection setting and fixed for the life of the file — re-upload to change it.',
183
+ condition: (_data, siblingData)=>isDocumentPrivate({
184
+ imagekit: siblingData
185
+ }, isPrivate),
186
+ description: "Indicates whether this file was uploaded to ImageKit as private. Private files are delivered through signed URLs. Changes to this collection's private option in payload.config only affect future uploads.",
160
187
  readOnly: true
161
188
  },
162
189
  label: 'Private file'
@@ -200,7 +227,8 @@ const imagekitAdapter = ({ apiRoute, client, clientUploads, privateSlugs, rootFo
200
227
  }
201
228
  }
202
229
  ],
203
- interfaceName: 'imagekitFields'
230
+ interfaceName: 'imagekitFields',
231
+ label: 'ImageKit'
204
232
  }
205
233
  ],
206
234
  generateURL: ({ filename })=>client.helper.buildSrc({
@@ -252,13 +280,44 @@ const imagekitAdapter = ({ apiRoute, client, clientUploads, privateSlugs, rootFo
252
280
  const hasClientUploadContext = 'clientUploadContext' in args.params;
253
281
  if (hasClientUploadContext) {
254
282
  const clientUploadContext = args.params.clientUploadContext;
255
- const clientUploadURL = isImageKitClientUploadContext(clientUploadContext) && typeof clientUploadContext.url === 'string' && clientUploadContext.url.length > 0 ? clientUploadContext.url : null;
256
- const filePath = isImageKitClientUploadContext(clientUploadContext) && typeof clientUploadContext.filePath === 'string' && clientUploadContext.filePath.length > 0 ? clientUploadContext.filePath : getFilePath(rootFolder, args.params.collection, args.params.filename);
257
- const imagekitURL = isPrivate ? buildSignedURL(filePath, tr) : clientUploadURL || client.helper.buildSrc({
258
- src: filePath,
283
+ if (!isImageKitClientUploadContext(clientUploadContext)) {
284
+ throw new Error('imagekitStorage: client upload context is invalid.');
285
+ }
286
+ const expectedFilePath = getFilePath(rootFolder, args.params.collection, args.params.filename);
287
+ const fileId = clientUploadContext.fileId;
288
+ if (typeof fileId !== 'string' || !fileId) {
289
+ throw new Error('imagekitStorage: client upload is missing its file ID.');
290
+ }
291
+ const uploadedFile = await client.files.get(fileId);
292
+ const uploadedFileMime = uploadedFile.mime;
293
+ const uploadedFileSize = uploadedFile.size;
294
+ const metadataMatchesUpload = uploadedFile.fileId === fileId && uploadedFile.filePath === expectedFilePath && uploadedFile.name === args.params.filename && typeof uploadedFileMime === 'string' && typeof uploadedFile.url === 'string' && typeof uploadedFileSize === 'number' && Number.isSafeInteger(uploadedFileSize) && uploadedFileSize > 0 && Boolean(uploadedFile.isPrivateFile) === isPrivate && (typeof clientUploadContext.size !== 'number' || uploadedFileSize === clientUploadContext.size);
295
+ if (!metadataMatchesUpload || typeof uploadedFileMime !== 'string' || typeof uploadedFileSize !== 'number') {
296
+ throw new Error('imagekitStorage: client upload metadata did not match.');
297
+ }
298
+ // Replace browser-provided values before hooks persist the upload context.
299
+ Object.assign(clientUploadContext, {
300
+ name: uploadedFile.name,
301
+ fileId: uploadedFile.fileId,
302
+ filePath: uploadedFile.filePath,
303
+ fileType: uploadedFile.fileType,
304
+ mimeType: uploadedFileMime,
305
+ size: uploadedFileSize,
306
+ thumbnailUrl: uploadedFile.thumbnail,
307
+ url: uploadedFile.url
308
+ });
309
+ const verifiedFileURL = isPrivate ? buildSignedURL(expectedFilePath, tr) : client.helper.buildSrc({
310
+ src: expectedFilePath,
259
311
  urlEndpoint
260
312
  });
261
- return fetchUploadedFile(imagekitURL);
313
+ if (isVideoMimeType(uploadedFileMime)) {
314
+ return fetchUploadedFilePrefix({
315
+ fileSize: uploadedFileSize,
316
+ fileURL: verifiedFileURL,
317
+ mimeType: uploadedFileMime
318
+ });
319
+ }
320
+ return fetchUploadedFile(verifiedFileURL);
262
321
  }
263
322
  const doc = args.doc || await findUploadDocumentByFilename({
264
323
  collection: args.params.collection,
@@ -273,12 +332,14 @@ const imagekitAdapter = ({ apiRoute, client, clientUploads, privateSlugs, rootFo
273
332
  let signSrc = getFilePath(rootFolder, args.params.collection, args.params.filename);
274
333
  // A `tr` request for a video/PDF is a thumbnail request (the plain transform
275
334
  // can't render a preview); serve ImageKit's generated thumbnail instead.
276
- if (tr && isVideoOrPdfMimeType(mimeType)) {
277
- signSrc = `${signSrc}/ik-thumbnail.jpg`;
335
+ if (tr && isVideoMimeType(mimeType)) {
336
+ signSrc = getVideoPosterSrc(signSrc);
337
+ } else if (tr && isPdfMimeType(mimeType)) {
338
+ signSrc = getImageKitThumbnailSrc(signSrc);
278
339
  }
279
340
  return Response.redirect(buildSignedURL(signSrc, tr), 302);
280
341
  }
281
- const imagekitURL = client.helper.buildSrc({
342
+ const imageKitURL = client.helper.buildSrc({
282
343
  src: getStoredImageKitURL(doc) || getFilePath(rootFolder, args.params.collection, args.params.filename),
283
344
  transformation: tr ? [
284
345
  {
@@ -287,7 +348,7 @@ const imagekitAdapter = ({ apiRoute, client, clientUploads, privateSlugs, rootFo
287
348
  ] : undefined,
288
349
  urlEndpoint
289
350
  });
290
- return Response.redirect(imagekitURL, 302);
351
+ return Response.redirect(imageKitURL, 302);
291
352
  }
292
353
  };
293
354
  };
@@ -305,7 +366,7 @@ const toCloudStorageCollections = (collections, adapter, privateSlugs, disablePa
305
366
  return mappedCollections;
306
367
  };
307
368
  export const imagekitStorage = (pluginOptions)=>(config)=>{
308
- const { alwaysInsertFields, clientUploads, collections, disablePayloadAccessControl, enabled, folder, privateKey, publicKey, signedUrlExpiresIn: signedUrlExpiresInOption, urlEndpoint } = pluginOptions;
369
+ const { alwaysInsertFields, clientUploads, collections, disablePayloadAccessControl, enabled, focalPoint, folder, privateKey, publicKey, signedUrlExpiresIn: signedUrlExpiresInOption, urlEndpoint } = pluginOptions;
309
370
  if (!privateKey?.trim()) {
310
371
  throw new Error('imagekitStorage: `privateKey` is required.');
311
372
  }
@@ -325,12 +386,12 @@ export const imagekitStorage = (pluginOptions)=>(config)=>{
325
386
  const signedUrlExpiresIn = typeof signedUrlExpiresInOption === 'number' && signedUrlExpiresInOption > 0 ? signedUrlExpiresInOption : DEFAULT_SIGNED_URL_EXPIRES_IN;
326
387
  const apiRoute = config.routes?.api ?? '/api';
327
388
  const rootFolder = normalizeFolder(folder);
328
- const imagekitClient = new ImageKit({
389
+ const imageKitClient = new ImageKit({
329
390
  privateKey
330
391
  });
331
392
  const adapter = imagekitAdapter({
332
393
  apiRoute,
333
- client: imagekitClient,
394
+ client: imageKitClient,
334
395
  clientUploads,
335
396
  privateSlugs,
336
397
  rootFolder,
@@ -347,6 +408,8 @@ export const imagekitStorage = (pluginOptions)=>(config)=>{
347
408
  const isCollectionPrivate = privateSlugs.has(collection.slug);
348
409
  const buildThumbnailURL = (doc)=>isDocumentPrivate(doc, isCollectionPrivate) ? getPrivateAdminThumbnailURL(doc, collection.slug, apiRoute) : getAdminThumbnailURL(doc);
349
410
  const beforeDocumentControls = collection.admin?.components?.edit?.beforeDocumentControls || [];
411
+ const collectionOptions = collections[collection.slug];
412
+ const collectionFocalPoint = collectionOptions !== true && typeof collectionOptions === 'object' && typeof collectionOptions.focalPoint === 'boolean' ? collectionOptions.focalPoint : undefined;
350
413
  // Surface a "this is a private collection" note in the list and edit views.
351
414
  // Preserve an existing string description by appending; leave custom
352
415
  // component descriptions untouched.
@@ -392,6 +455,17 @@ export const imagekitStorage = (pluginOptions)=>(config)=>{
392
455
  ]
393
456
  },
394
457
  upload: {
458
+ // Focal points are opt-in for configured collections. Plugin-level
459
+ // and collection-level options can override this default; a
460
+ // collection's own `upload.focalPoint` still wins because it is
461
+ // spread in afterwards.
462
+ focalPoint: false,
463
+ ...typeof focalPoint === 'boolean' ? {
464
+ focalPoint
465
+ } : {},
466
+ ...typeof collectionFocalPoint === 'boolean' ? {
467
+ focalPoint: collectionFocalPoint
468
+ } : {},
395
469
  ...typeof collection.upload === 'object' ? collection.upload : {},
396
470
  adminThumbnail: ({ doc })=>buildThumbnailURL(doc),
397
471
  crop: false
@@ -402,7 +476,7 @@ export const imagekitStorage = (pluginOptions)=>(config)=>{
402
476
  ...config.endpoints || [],
403
477
  {
404
478
  handler: createImageKitUploadAuthHandler({
405
- client: imagekitClient,
479
+ client: imageKitClient,
406
480
  collections
407
481
  }),
408
482
  method: 'get',
@@ -419,5 +493,6 @@ export const imagekitStorage = (pluginOptions)=>(config)=>{
419
493
  export { migrateFolder } from './migrate.js';
420
494
  // Backward-compatible alias
421
495
  export const payloadStorageImagekit = imagekitStorage;
496
+ export const payloadStorageImageKit = imagekitStorage;
422
497
 
423
498
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Adapter, CollectionOptions } from '@payloadcms/plugin-cloud-storage/types'\nimport type {\n CollectionSlug,\n Config,\n PayloadRequest,\n TypeWithID,\n UploadCollectionSlug,\n Where,\n} from 'payload'\n\nimport ImageKit, { toFile } from '@imagekit/nodejs'\nimport { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'\nimport path from 'node:path'\n\nimport { getImageKitUploadAuthPath, isImageKitClientUploadContext } from './clientUploads.js'\nimport { createImageKitUploadAuthHandler } from './endpoints/createImageKitUploadAuthHandler.js'\nimport {\n getAdminThumbnailURL,\n getPrivateAdminThumbnailURL,\n isVideoOrPdfMimeType,\n} from './getAdminThumbnailURL.js'\n\nexport type ImageKitCollectionOptions = {\n /**\n * Upload files in this collection as ImageKit private files (`isPrivateFile: true`)\n * and deliver them only through short-lived signed URLs, behind Payload access control.\n */\n private?: boolean\n}\ntype ImageKitCollectionConfig = ImageKitCollectionOptions | true\ntype ImageKitCollectionMap = Partial<Record<UploadCollectionSlug, ImageKitCollectionConfig>>\n\nconst DEFAULT_SIGNED_URL_EXPIRES_IN = 900\n\nconst PRIVATE_COLLECTION_DESCRIPTION =\n 'Files in this collection are uploaded to ImageKit as private files and delivered through short-lived signed URLs.'\n\nconst isPrivateCollectionConfig = (value: ImageKitCollectionConfig | undefined): boolean =>\n value !== true && typeof value === 'object' && value?.private === true\n\n/**\n * Whether a specific document is private. The flag stored on the document at upload time\n * (`imagekit.isPrivate`) is the source of truth — a file's privacy on ImageKit is fixed\n * when it is uploaded and does not change if the collection's `private` setting changes\n * later. Falls back to the collection setting for documents uploaded before this flag existed.\n */\nconst isDocumentPrivate = (doc: unknown, collectionIsPrivate: boolean): boolean => {\n const stored = (doc as { imagekit?: { isPrivate?: boolean } } | undefined)?.imagekit?.isPrivate\n\n return typeof stored === 'boolean' ? stored : collectionIsPrivate\n}\ntype ImagekitReference = {\n imagekit?: {\n fileId?: string\n isPrivate?: boolean\n thumbnailUrl?: string\n url?: string\n }\n}\n\ntype UploadDocumentData = {\n thumbnailURL?: string\n} & ImagekitReference\n\nexport type PayloadStorageImagekitConfig = {\n alwaysInsertFields?: boolean\n clientUploads?: boolean\n collections: ImageKitCollectionMap\n disablePayloadAccessControl?: true\n enabled?: boolean\n folder: string\n privateKey: string\n publicKey: string\n /**\n * Expiry (in seconds) applied to signed URLs generated for private collections.\n * Defaults to 900 (15 minutes). URLs are signed fresh on every request, so a short\n * window is safe.\n */\n signedUrlExpiresIn?: number\n urlEndpoint: string\n}\n\nconst normalizeFolder = (folder: string): string => {\n const normalized = folder.trim().replace(/^\\/+|\\/+$/g, '')\n\n if (!normalized) {\n throw new Error('imagekitStorage: `folder` is required.')\n }\n\n return `/${normalized}`\n}\n\nconst getCollectionFolder = (rootFolder: string, collectionSlug: string): string =>\n path.posix.join(rootFolder, collectionSlug)\n\nconst getFilePath = (rootFolder: string, collectionSlug: string, filename: string): string =>\n path.posix.join(getCollectionFolder(rootFolder, collectionSlug), filename)\n\nconst findImageKitFileId = async (\n imagekit: ImageKit,\n filePath: string,\n): Promise<string | undefined> => {\n const parentPath = path.posix.dirname(filePath)\n const name = path.posix.basename(filePath)\n\n const limit = 100\n\n for (let skip = 0; ; skip += limit) {\n const assets = await imagekit.assets.list({\n type: 'file',\n limit,\n path: parentPath === '/' ? '/' : `${parentPath}/`,\n skip,\n })\n\n const found = assets.find(\n (asset) => 'fileId' in asset && asset.filePath === filePath && asset.name === name,\n )\n\n if (found && 'fileId' in found) {\n return found.fileId\n }\n\n if (assets.length < limit) {\n break\n }\n }\n\n return undefined\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n !!value && typeof value === 'object'\n\nconst getStoredImageKitURL = (doc: unknown): string | undefined => {\n const storedURL = (doc as ImagekitReference | undefined)?.imagekit?.url\n\n return typeof storedURL === 'string' && storedURL.trim().length > 0 ? storedURL : undefined\n}\n\nconst findUploadDocumentByFilename = async ({\n collection,\n filename,\n prefix,\n req,\n}: {\n collection: string\n filename: string\n prefix?: string\n req: PayloadRequest\n}): Promise<null | TypeWithID> => {\n const constraints: Where[] = [\n {\n filename: {\n equals: filename,\n },\n },\n ]\n\n if (typeof prefix === 'string') {\n constraints.push({\n prefix: {\n equals: prefix,\n },\n })\n }\n\n return req.payload.db.findOne({\n collection,\n req,\n where: {\n and: constraints,\n },\n })\n}\n\nconst applyClientUploadContextToData = <T extends UploadDocumentData>(\n data: T,\n clientUploadContext: unknown,\n buildThumbnailURL: (data: T) => string = getAdminThumbnailURL,\n isPrivate = false,\n): T => {\n if (!isImageKitClientUploadContext(clientUploadContext)) {\n return data\n }\n\n data.imagekit = {\n fileId: clientUploadContext.fileId,\n // Record the collection's privacy at upload time; this is the file's permanent\n // ImageKit status and the source of truth for delivery.\n isPrivate,\n thumbnailUrl: clientUploadContext.thumbnailUrl,\n url: clientUploadContext.url,\n }\n\n // Preserve the admin preview when the original file was already uploaded client-side.\n data.thumbnailURL = buildThumbnailURL(data)\n\n return data\n}\n\nconst imagekitAdapter = ({\n apiRoute,\n client,\n clientUploads,\n privateSlugs,\n rootFolder,\n signedUrlExpiresIn,\n urlEndpoint,\n}: {\n apiRoute: string\n client: ImageKit\n clientUploads?: boolean\n privateSlugs: Set<string>\n rootFolder: string\n signedUrlExpiresIn: number\n urlEndpoint: string\n}): Adapter => {\n const fetchUploadedFile = async (fileURL: string): Promise<Response> => {\n const response = await fetch(fileURL)\n\n if (!response.ok) {\n throw new Error(\n `imagekitStorage: failed to fetch uploaded file from ImageKit (${response.status}).`,\n )\n }\n\n const fileBuffer = await response.arrayBuffer()\n const headers = new Headers()\n const contentType = response.headers.get('Content-Type')\n\n if (contentType) {\n headers.set('Content-Type', contentType)\n }\n\n headers.set('Content-Length', String(fileBuffer.byteLength))\n\n return new Response(fileBuffer, {\n headers,\n status: 200,\n statusText: response.statusText,\n })\n }\n\n return ({ collection }) => {\n const isPrivate = privateSlugs.has(collection.slug)\n\n // Sign a relative ImageKit path against the configured urlEndpoint. We always sign\n // a computed path (never a stored absolute URL) because ImageKit's signature is\n // derived from the path after stripping urlEndpoint, so the host must match.\n const buildSignedURL = (src: string, tr?: null | string): string =>\n client.helper.buildSrc({\n expiresIn: signedUrlExpiresIn,\n signed: true,\n src,\n transformation: tr ? [{ raw: tr }] : undefined,\n urlEndpoint,\n })\n\n const buildThumbnailURL = (doc: UploadDocumentData): string =>\n isDocumentPrivate(doc, isPrivate)\n ? getPrivateAdminThumbnailURL(doc, collection.slug, apiRoute)\n : getAdminThumbnailURL(doc)\n\n return {\n name: 'imagekit',\n clientUploads,\n fields: [\n {\n name: 'imagekit',\n type: 'group',\n admin: {\n disableListColumn: true,\n disableListFilter: true,\n },\n fields: [\n {\n name: 'fileId',\n type: 'text',\n admin: {\n hidden: true,\n },\n },\n {\n name: 'url',\n type: 'text',\n admin: {\n readOnly: true,\n },\n },\n {\n name: 'isPrivate',\n type: 'checkbox',\n admin: {\n description:\n 'Whether this file is stored as a private file on ImageKit. Set at upload time from the collection setting and fixed for the life of the file — re-upload to change it.',\n readOnly: true,\n },\n label: 'Private file',\n },\n {\n name: 'signedUrl',\n type: 'text',\n admin: {\n // Only relevant for private files; the plain `url` works directly for public ones.\n condition: (_data, siblingData) =>\n isDocumentPrivate({ imagekit: siblingData }, isPrivate),\n description: `Temporary signed URL for reaching this private file directly on ImageKit. Expires ${signedUrlExpiresIn} seconds after load and is regenerated on every read. Note: anyone with this URL can access the file until it expires — it is not gated by Payload access control.`,\n readOnly: true,\n },\n hooks: {\n // Computed server-side on read so it is always freshly signed and never persisted.\n afterRead: [\n ({ data, siblingData }) => {\n if (!isDocumentPrivate({ imagekit: siblingData }, isPrivate)) {\n return undefined\n }\n\n const filename = (data as { filename?: string } | undefined)?.filename\n\n if (!filename) {\n return undefined\n }\n\n return buildSignedURL(getFilePath(rootFolder, collection.slug, filename))\n },\n ],\n },\n label: 'Signed URL',\n virtual: true,\n },\n {\n name: 'thumbnailUrl',\n type: 'text',\n admin: {\n hidden: true,\n },\n },\n ],\n interfaceName: 'imagekitFields',\n },\n ],\n generateURL: ({ filename }) =>\n client.helper.buildSrc({\n src: getFilePath(rootFolder, collection.slug, filename),\n urlEndpoint,\n }),\n handleDelete: async ({ doc, filename }) => {\n const storedFileId = (doc as ImagekitReference & typeof doc).imagekit?.fileId\n const fileId =\n storedFileId ||\n (await findImageKitFileId(client, getFilePath(rootFolder, collection.slug, filename)))\n\n if (!fileId) {\n return\n }\n\n await client.files.delete(fileId)\n },\n handleUpload: async ({ clientUploadContext, data, file }) => {\n if (isImageKitClientUploadContext(clientUploadContext)) {\n return applyClientUploadContextToData(\n data,\n clientUploadContext,\n buildThumbnailURL,\n isPrivate,\n )\n }\n\n const uploadFile = await toFile(file.buffer, file.filename, { type: file.mimeType })\n\n const uploaded = await client.files.upload({\n file: uploadFile,\n fileName: file.filename,\n folder: getCollectionFolder(rootFolder, collection.slug),\n useUniqueFileName: false,\n ...(isPrivate ? { isPrivateFile: true } : {}),\n })\n\n data.imagekit = {\n fileId: uploaded.fileId,\n isPrivate,\n thumbnailUrl: uploaded.thumbnailUrl,\n url: uploaded.url,\n }\n\n // Set the root-level thumbnailURL that Payload's admin interface expects.\n // `buildThumbnailURL` needs the filename, which Payload may not have copied onto\n // `data` yet at this point, so fall back to the uploaded file's name.\n data.thumbnailURL = buildThumbnailURL({\n ...data,\n filename: (data as { filename?: string }).filename || file.filename,\n })\n\n return data\n },\n staticHandler: async (req, args) => {\n const requestURL = new URL(req.url || 'http://localhost')\n const tr = requestURL.searchParams.get('tr')\n const hasClientUploadContext = 'clientUploadContext' in args.params\n\n if (hasClientUploadContext) {\n const clientUploadContext = args.params.clientUploadContext\n const clientUploadURL =\n isImageKitClientUploadContext(clientUploadContext) &&\n typeof clientUploadContext.url === 'string' &&\n clientUploadContext.url.length > 0\n ? clientUploadContext.url\n : null\n const filePath =\n isImageKitClientUploadContext(clientUploadContext) &&\n typeof clientUploadContext.filePath === 'string' &&\n clientUploadContext.filePath.length > 0\n ? clientUploadContext.filePath\n : getFilePath(rootFolder, args.params.collection, args.params.filename)\n const imagekitURL = isPrivate\n ? buildSignedURL(filePath, tr)\n : clientUploadURL ||\n client.helper.buildSrc({\n src: filePath,\n urlEndpoint,\n })\n\n return fetchUploadedFile(imagekitURL)\n }\n\n const doc =\n args.doc ||\n (await findUploadDocumentByFilename({\n collection: args.params.collection,\n filename: args.params.filename,\n prefix: args.params.prefix,\n req,\n }))\n\n // Sign based on the document's stored status, not the collection's current\n // setting, so files keep working if the collection's privacy is changed later.\n if (isDocumentPrivate(doc, isPrivate)) {\n const mimeType = (doc as { mimeType?: string } | null)?.mimeType\n let signSrc = getFilePath(rootFolder, args.params.collection, args.params.filename)\n\n // A `tr` request for a video/PDF is a thumbnail request (the plain transform\n // can't render a preview); serve ImageKit's generated thumbnail instead.\n if (tr && isVideoOrPdfMimeType(mimeType)) {\n signSrc = `${signSrc}/ik-thumbnail.jpg`\n }\n\n return Response.redirect(buildSignedURL(signSrc, tr), 302)\n }\n\n const imagekitURL = client.helper.buildSrc({\n src:\n getStoredImageKitURL(doc) ||\n getFilePath(rootFolder, args.params.collection, args.params.filename),\n transformation: tr ? [{ raw: tr }] : undefined,\n urlEndpoint,\n })\n\n return Response.redirect(imagekitURL, 302)\n },\n }\n }\n}\n\nconst toCloudStorageCollections = (\n collections: ImageKitCollectionMap,\n adapter: Adapter,\n privateSlugs: Set<string>,\n disablePayloadAccessControl?: true,\n): Partial<Record<CollectionSlug, CollectionOptions>> => {\n const mappedCollections: Partial<Record<CollectionSlug, CollectionOptions>> = {}\n\n for (const collectionSlug of Object.keys(collections)) {\n mappedCollections[collectionSlug] = {\n adapter,\n disableLocalStorage: true,\n // Private files require Payload access control so URLs can be signed at serve time.\n disablePayloadAccessControl: privateSlugs.has(collectionSlug)\n ? undefined\n : disablePayloadAccessControl,\n }\n }\n\n return mappedCollections\n}\n\nexport const imagekitStorage =\n (pluginOptions: PayloadStorageImagekitConfig) =>\n (config: Config): Config => {\n const {\n alwaysInsertFields,\n clientUploads,\n collections,\n disablePayloadAccessControl,\n enabled,\n folder,\n privateKey,\n publicKey,\n signedUrlExpiresIn: signedUrlExpiresInOption,\n urlEndpoint,\n } = pluginOptions\n\n if (!privateKey?.trim()) {\n throw new Error('imagekitStorage: `privateKey` is required.')\n }\n\n if (!publicKey?.trim()) {\n throw new Error('imagekitStorage: `publicKey` is required.')\n }\n\n if (!urlEndpoint?.trim()) {\n throw new Error('imagekitStorage: `urlEndpoint` is required.')\n }\n\n if (!collections || Object.keys(collections).length === 0) {\n throw new Error('imagekitStorage: `collections` must include at least one upload collection.')\n }\n\n const privateSlugs = new Set(\n Object.entries(collections)\n .filter(([, value]) => isPrivateCollectionConfig(value))\n .map(([slug]) => slug),\n )\n\n if (disablePayloadAccessControl && privateSlugs.size > 0) {\n throw new Error(\n 'imagekitStorage: `disablePayloadAccessControl` cannot be used with private collections. Private files require Payload access control to remain enabled so URLs can be signed at serve time.',\n )\n }\n\n const signedUrlExpiresIn =\n typeof signedUrlExpiresInOption === 'number' && signedUrlExpiresInOption > 0\n ? signedUrlExpiresInOption\n : DEFAULT_SIGNED_URL_EXPIRES_IN\n const apiRoute = config.routes?.api ?? '/api'\n\n const rootFolder = normalizeFolder(folder)\n\n const imagekitClient = new ImageKit({ privateKey })\n const adapter = imagekitAdapter({\n apiRoute,\n client: imagekitClient,\n clientUploads,\n privateSlugs,\n rootFolder,\n signedUrlExpiresIn,\n urlEndpoint,\n })\n\n const configuredSlugs = new Set(Object.keys(collections))\n const configWithCropDisabled: Config = {\n ...config,\n collections: (config.collections || []).map((collection) => {\n if (!configuredSlugs.has(collection.slug)) {\n return collection\n }\n\n const isCollectionPrivate = privateSlugs.has(collection.slug)\n const buildThumbnailURL = (doc: unknown): string =>\n isDocumentPrivate(doc, isCollectionPrivate)\n ? getPrivateAdminThumbnailURL(doc, collection.slug, apiRoute)\n : getAdminThumbnailURL(doc)\n\n const beforeDocumentControls =\n collection.admin?.components?.edit?.beforeDocumentControls || []\n\n // Surface a \"this is a private collection\" note in the list and edit views.\n // Preserve an existing string description by appending; leave custom\n // component descriptions untouched.\n const existingDescription = collection.admin?.description\n const baseAdmin = isCollectionPrivate\n ? {\n ...(collection.admin || {}),\n description:\n typeof existingDescription === 'string' && existingDescription.trim().length > 0\n ? `${existingDescription} ${PRIVATE_COLLECTION_DESCRIPTION}`\n : (existingDescription ?? PRIVATE_COLLECTION_DESCRIPTION),\n }\n : collection.admin\n\n return {\n ...collection,\n admin: clientUploads\n ? {\n ...(baseAdmin || {}),\n components: {\n ...(collection.admin?.components || {}),\n edit: {\n ...(collection.admin?.components?.edit || {}),\n beforeDocumentControls: [\n ...beforeDocumentControls,\n {\n clientProps: {\n authPath: getImageKitUploadAuthPath(collection.slug),\n collectionSlug: collection.slug,\n folder: getCollectionFolder(rootFolder, collection.slug),\n isPrivate: isCollectionPrivate,\n publicKey,\n },\n path: '@humaan/payload-storage-imagekit/client#ImageKitUploadHandler',\n },\n ],\n },\n },\n }\n : baseAdmin,\n hooks: {\n ...(collection.hooks || {}),\n beforeChange: [\n ...(collection.hooks?.beforeChange || []),\n ({ data, req }) => {\n if (!isRecord(data) || !req.file?.clientUploadContext) {\n return data\n }\n\n return applyClientUploadContextToData(\n data,\n req.file.clientUploadContext,\n buildThumbnailURL,\n isCollectionPrivate,\n )\n },\n ],\n },\n upload: {\n ...(typeof collection.upload === 'object' ? collection.upload : {}),\n adminThumbnail: ({ doc }) => buildThumbnailURL(doc),\n crop: false,\n },\n }\n }),\n endpoints: clientUploads\n ? [\n ...(config.endpoints || []),\n {\n handler: createImageKitUploadAuthHandler({\n client: imagekitClient,\n collections,\n }),\n method: 'get',\n path: `${getImageKitUploadAuthPath(':collection')}`,\n },\n ]\n : config.endpoints,\n }\n\n return cloudStoragePlugin({\n alwaysInsertFields,\n collections: toCloudStorageCollections(\n collections,\n adapter,\n privateSlugs,\n disablePayloadAccessControl,\n ),\n enabled,\n })(configWithCropDisabled)\n }\n\nexport { migrateFolder } from './migrate.js'\nexport type { MigrateFolderOptions, MigrationJobResult } from './migrate.js'\n\n// Backward-compatible alias\nexport const payloadStorageImagekit = imagekitStorage\n"],"names":["ImageKit","toFile","cloudStoragePlugin","path","getImageKitUploadAuthPath","isImageKitClientUploadContext","createImageKitUploadAuthHandler","getAdminThumbnailURL","getPrivateAdminThumbnailURL","isVideoOrPdfMimeType","DEFAULT_SIGNED_URL_EXPIRES_IN","PRIVATE_COLLECTION_DESCRIPTION","isPrivateCollectionConfig","value","private","isDocumentPrivate","doc","collectionIsPrivate","stored","imagekit","isPrivate","normalizeFolder","folder","normalized","trim","replace","Error","getCollectionFolder","rootFolder","collectionSlug","posix","join","getFilePath","filename","findImageKitFileId","filePath","parentPath","dirname","name","basename","limit","skip","assets","list","type","found","find","asset","fileId","length","undefined","isRecord","getStoredImageKitURL","storedURL","url","findUploadDocumentByFilename","collection","prefix","req","constraints","equals","push","payload","db","findOne","where","and","applyClientUploadContextToData","data","clientUploadContext","buildThumbnailURL","thumbnailUrl","thumbnailURL","imagekitAdapter","apiRoute","client","clientUploads","privateSlugs","signedUrlExpiresIn","urlEndpoint","fetchUploadedFile","fileURL","response","fetch","ok","status","fileBuffer","arrayBuffer","headers","Headers","contentType","get","set","String","byteLength","Response","statusText","has","slug","buildSignedURL","src","tr","helper","buildSrc","expiresIn","signed","transformation","raw","fields","admin","disableListColumn","disableListFilter","hidden","readOnly","description","label","condition","_data","siblingData","hooks","afterRead","virtual","interfaceName","generateURL","handleDelete","storedFileId","files","delete","handleUpload","file","uploadFile","buffer","mimeType","uploaded","upload","fileName","useUniqueFileName","isPrivateFile","staticHandler","args","requestURL","URL","searchParams","hasClientUploadContext","params","clientUploadURL","imagekitURL","signSrc","redirect","toCloudStorageCollections","collections","adapter","disablePayloadAccessControl","mappedCollections","Object","keys","disableLocalStorage","imagekitStorage","pluginOptions","config","alwaysInsertFields","enabled","privateKey","publicKey","signedUrlExpiresInOption","Set","entries","filter","map","size","routes","api","imagekitClient","configuredSlugs","configWithCropDisabled","isCollectionPrivate","beforeDocumentControls","components","edit","existingDescription","baseAdmin","clientProps","authPath","beforeChange","adminThumbnail","crop","endpoints","handler","method","migrateFolder","payloadStorageImagekit"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Adapter, CollectionOptions } from '@payloadcms/plugin-cloud-storage/types'\nimport type {\n CollectionSlug,\n Config,\n PayloadRequest,\n TypeWithID,\n UploadCollectionSlug,\n Where,\n} from 'payload'\n\nimport ImageKit, { toFile } from '@imagekit/nodejs'\nimport { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'\nimport path from 'node:path'\n\nimport { getImageKitUploadAuthPath, isImageKitClientUploadContext } from './clientUploads.js'\nimport { createImageKitUploadAuthHandler } from './endpoints/createImageKitUploadAuthHandler.js'\nimport {\n getAdminThumbnailURL,\n getPrivateAdminThumbnailURL,\n isPdfMimeType,\n isVideoMimeType,\n} from './getAdminThumbnailURL.js'\nimport { getImageKitThumbnailSrc, getVideoPosterSrc } from './imageTransforms.js'\n\nexport type ImageKitCollectionOptions = {\n /**\n * Override Payload's native focal-point picker (`upload.focalPoint`) for this\n * collection.\n */\n focalPoint?: boolean\n /**\n * Upload files in this collection as ImageKit private files (`isPrivateFile: true`)\n * and deliver them only through short-lived signed URLs, behind Payload access control.\n */\n private?: boolean\n}\ntype ImageKitCollectionConfig = ImageKitCollectionOptions | true\ntype ImageKitCollectionMap = Partial<Record<UploadCollectionSlug, ImageKitCollectionConfig>>\n\nconst DEFAULT_SIGNED_URL_EXPIRES_IN = 900\n// Payload's file-type detector samples this many bytes when validating binary uploads.\nconst FILE_TYPE_DETECTION_SIZE = 4100\nconst FILE_TYPE_DETECTION_RANGE = `bytes=0-${FILE_TYPE_DETECTION_SIZE - 1}`\n\nconst PRIVATE_COLLECTION_DESCRIPTION =\n 'Files in this collection are uploaded to ImageKit as private files and delivered through short-lived signed URLs.'\n\nconst isPrivateCollectionConfig = (value: ImageKitCollectionConfig | undefined): boolean =>\n value !== true && typeof value === 'object' && value?.private === true\n\n/**\n * Whether a specific document is private. The flag stored on the document at upload time\n * (`imagekit.isPrivate`) is the source of truth — a file's privacy on ImageKit is fixed\n * when it is uploaded and does not change if the collection's `private` setting changes\n * later. Falls back to the collection setting for documents uploaded before this flag existed.\n */\nconst isDocumentPrivate = (doc: unknown, collectionIsPrivate: boolean): boolean => {\n const stored = (doc as { imagekit?: { isPrivate?: boolean } } | undefined)?.imagekit?.isPrivate\n\n return typeof stored === 'boolean' ? stored : collectionIsPrivate\n}\ntype ImageKitReference = {\n imagekit?: {\n fileId?: string\n isPrivate?: boolean\n thumbnailUrl?: string\n url?: string\n }\n}\n\ntype UploadDocumentData = {\n thumbnailURL?: string\n} & ImageKitReference\n\nexport type PayloadStorageImageKitConfig = {\n alwaysInsertFields?: boolean\n clientUploads?: boolean\n collections: ImageKitCollectionMap\n disablePayloadAccessControl?: true\n enabled?: boolean\n /**\n * Broad default for Payload's native focal-point picker (`upload.focalPoint`)\n * on every configured collection. Focal points are disabled by default for\n * configured collections, so prefer collection-level `focalPoint: true` for\n * image collections. A collection's own `upload.focalPoint` still takes\n * precedence.\n */\n focalPoint?: boolean\n folder: string\n privateKey: string\n publicKey: string\n /**\n * Expiry (in seconds) applied to signed URLs generated for private collections.\n * Defaults to 900 (15 minutes). URLs are signed fresh on every request, so a short\n * window is safe.\n */\n signedUrlExpiresIn?: number\n urlEndpoint: string\n}\n\nconst normalizeFolder = (folder: string): string => {\n const normalized = folder.trim().replace(/^\\/+|\\/+$/g, '')\n\n if (!normalized) {\n throw new Error('imagekitStorage: `folder` is required.')\n }\n\n return `/${normalized}`\n}\n\nconst getCollectionFolder = (rootFolder: string, collectionSlug: string): string =>\n path.posix.join(rootFolder, collectionSlug)\n\nconst getFilePath = (rootFolder: string, collectionSlug: string, filename: string): string =>\n path.posix.join(getCollectionFolder(rootFolder, collectionSlug), filename)\n\nconst findImageKitFileId = async (\n imagekit: ImageKit,\n filePath: string,\n): Promise<string | undefined> => {\n const parentPath = path.posix.dirname(filePath)\n const name = path.posix.basename(filePath)\n\n const limit = 100\n\n for (let skip = 0; ; skip += limit) {\n const assets = await imagekit.assets.list({\n type: 'file',\n limit,\n path: parentPath === '/' ? '/' : `${parentPath}/`,\n skip,\n })\n\n const found = assets.find(\n (asset) => 'fileId' in asset && asset.filePath === filePath && asset.name === name,\n )\n\n if (found && 'fileId' in found) {\n return found.fileId\n }\n\n if (assets.length < limit) {\n break\n }\n }\n\n return undefined\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n !!value && typeof value === 'object'\n\nconst getStoredImageKitURL = (doc: unknown): string | undefined => {\n const storedURL = (doc as ImageKitReference | undefined)?.imagekit?.url\n\n return typeof storedURL === 'string' && storedURL.trim().length > 0 ? storedURL : undefined\n}\n\nconst hasStoredImageKitURL = (doc: unknown): boolean => !!getStoredImageKitURL(doc)\n\nconst findUploadDocumentByFilename = async ({\n collection,\n filename,\n prefix,\n req,\n}: {\n collection: string\n filename: string\n prefix?: string\n req: PayloadRequest\n}): Promise<null | TypeWithID> => {\n const constraints: Where[] = [\n {\n filename: {\n equals: filename,\n },\n },\n ]\n\n if (typeof prefix === 'string') {\n constraints.push({\n prefix: {\n equals: prefix,\n },\n })\n }\n\n return req.payload.db.findOne({\n collection,\n req,\n where: {\n and: constraints,\n },\n })\n}\n\nconst applyClientUploadContextToData = <T extends UploadDocumentData>(\n data: T,\n clientUploadContext: unknown,\n buildThumbnailURL: (data: T) => string = getAdminThumbnailURL,\n isPrivate = false,\n): T => {\n if (!isImageKitClientUploadContext(clientUploadContext)) {\n return data\n }\n\n data.imagekit = {\n fileId: clientUploadContext.fileId,\n // Record the collection's privacy at upload time; this is the file's permanent\n // ImageKit status and the source of truth for delivery.\n isPrivate,\n thumbnailUrl: clientUploadContext.thumbnailUrl,\n url: clientUploadContext.url,\n }\n\n // Preserve the admin preview when the original file was already uploaded client-side.\n data.thumbnailURL = buildThumbnailURL(data)\n\n return data\n}\n\nconst imagekitAdapter = ({\n apiRoute,\n client,\n clientUploads,\n privateSlugs,\n rootFolder,\n signedUrlExpiresIn,\n urlEndpoint,\n}: {\n apiRoute: string\n client: ImageKit\n clientUploads?: boolean\n privateSlugs: Set<string>\n rootFolder: string\n signedUrlExpiresIn: number\n urlEndpoint: string\n}): Adapter => {\n const fetchUploadedFile = async (fileURL: string, range?: string): Promise<Response> => {\n const response = range\n ? await fetch(fileURL, {\n headers: {\n Range: range,\n },\n })\n : await fetch(fileURL)\n\n if (!response.ok) {\n throw new Error(\n `imagekitStorage: failed to fetch uploaded file from ImageKit (${response.status}).`,\n )\n }\n\n return response\n }\n\n const fetchUploadedFilePrefix = async ({\n fileSize,\n fileURL,\n mimeType,\n }: {\n fileSize: number\n fileURL: string\n mimeType: string\n }): Promise<Response> => {\n const response = await fetchUploadedFile(fileURL, FILE_TYPE_DETECTION_RANGE)\n const contentLength = Number(response.headers.get('Content-Length'))\n const contentRange = response.headers.get('Content-Range')\n const rangeMatch = contentRange?.match(/^bytes 0-(\\d+)\\/(\\d+)$/)\n const rangeEnd = rangeMatch ? Number(rangeMatch[1]) : Number.NaN\n const totalSize = rangeMatch ? Number(rangeMatch[2]) : Number.NaN\n const expectedLength = Math.min(fileSize, FILE_TYPE_DETECTION_SIZE)\n\n if (\n response.status !== 206 ||\n !rangeMatch ||\n !Number.isSafeInteger(contentLength) ||\n contentLength !== expectedLength ||\n !Number.isSafeInteger(rangeEnd) ||\n rangeEnd + 1 !== contentLength ||\n totalSize !== fileSize\n ) {\n await response.body?.cancel()\n throw new Error('imagekitStorage: ImageKit returned an invalid video range response.')\n }\n\n const filePrefix = await response.arrayBuffer()\n\n if (filePrefix.byteLength !== contentLength) {\n throw new Error('imagekitStorage: ImageKit returned an invalid video range body.')\n }\n\n return new Response(filePrefix, {\n headers: {\n 'Content-Length': String(filePrefix.byteLength),\n 'Content-Type': mimeType,\n },\n status: 200,\n })\n }\n\n return ({ collection }) => {\n const isPrivate = privateSlugs.has(collection.slug)\n\n // Sign a relative ImageKit path against the configured urlEndpoint. We always sign\n // a computed path (never a stored absolute URL) because ImageKit's signature is\n // derived from the path after stripping urlEndpoint, so the host must match.\n const buildSignedURL = (src: string, tr?: null | string): string =>\n client.helper.buildSrc({\n expiresIn: signedUrlExpiresIn,\n signed: true,\n src,\n transformation: tr ? [{ raw: tr }] : undefined,\n urlEndpoint,\n })\n\n const buildThumbnailURL = (doc: UploadDocumentData): string =>\n isDocumentPrivate(doc, isPrivate)\n ? getPrivateAdminThumbnailURL(doc, collection.slug, apiRoute)\n : getAdminThumbnailURL(doc)\n\n return {\n name: 'imagekit',\n clientUploads,\n fields: [\n {\n name: 'imagekit',\n type: 'group',\n admin: {\n condition: (data) => hasStoredImageKitURL(data),\n disableListColumn: true,\n disableListFilter: true,\n },\n fields: [\n {\n name: 'fileId',\n type: 'text',\n admin: {\n hidden: true,\n },\n },\n {\n name: 'url',\n type: 'text',\n admin: {\n readOnly: true,\n },\n label: 'URL',\n },\n {\n name: 'isPrivate',\n type: 'checkbox',\n admin: {\n condition: (_data, siblingData) =>\n isDocumentPrivate({ imagekit: siblingData }, isPrivate),\n description:\n \"Indicates whether this file was uploaded to ImageKit as private. Private files are delivered through signed URLs. Changes to this collection's private option in payload.config only affect future uploads.\",\n readOnly: true,\n },\n label: 'Private file',\n },\n {\n name: 'signedUrl',\n type: 'text',\n admin: {\n // Only relevant for private files; the plain `url` works directly for public ones.\n condition: (_data, siblingData) =>\n isDocumentPrivate({ imagekit: siblingData }, isPrivate),\n description: `Temporary signed URL for reaching this private file directly on ImageKit. Expires ${signedUrlExpiresIn} seconds after load and is regenerated on every read. Note: anyone with this URL can access the file until it expires — it is not gated by Payload access control.`,\n readOnly: true,\n },\n hooks: {\n // Computed server-side on read so it is always freshly signed and never persisted.\n afterRead: [\n ({ data, siblingData }) => {\n if (!isDocumentPrivate({ imagekit: siblingData }, isPrivate)) {\n return undefined\n }\n\n const filename = (data as { filename?: string } | undefined)?.filename\n\n if (!filename) {\n return undefined\n }\n\n return buildSignedURL(getFilePath(rootFolder, collection.slug, filename))\n },\n ],\n },\n label: 'Signed URL',\n virtual: true,\n },\n {\n name: 'thumbnailUrl',\n type: 'text',\n admin: {\n hidden: true,\n },\n },\n ],\n interfaceName: 'imagekitFields',\n label: 'ImageKit',\n },\n ],\n generateURL: ({ filename }) =>\n client.helper.buildSrc({\n src: getFilePath(rootFolder, collection.slug, filename),\n urlEndpoint,\n }),\n handleDelete: async ({ doc, filename }) => {\n const storedFileId = (doc as ImageKitReference & typeof doc).imagekit?.fileId\n const fileId =\n storedFileId ||\n (await findImageKitFileId(client, getFilePath(rootFolder, collection.slug, filename)))\n\n if (!fileId) {\n return\n }\n\n await client.files.delete(fileId)\n },\n handleUpload: async ({ clientUploadContext, data, file }) => {\n if (isImageKitClientUploadContext(clientUploadContext)) {\n return applyClientUploadContextToData(\n data,\n clientUploadContext,\n buildThumbnailURL,\n isPrivate,\n )\n }\n\n const uploadFile = await toFile(file.buffer, file.filename, { type: file.mimeType })\n\n const uploaded = await client.files.upload({\n file: uploadFile,\n fileName: file.filename,\n folder: getCollectionFolder(rootFolder, collection.slug),\n useUniqueFileName: false,\n ...(isPrivate ? { isPrivateFile: true } : {}),\n })\n\n data.imagekit = {\n fileId: uploaded.fileId,\n isPrivate,\n thumbnailUrl: uploaded.thumbnailUrl,\n url: uploaded.url,\n }\n\n // Set the root-level thumbnailURL that Payload's admin interface expects.\n // `buildThumbnailURL` needs the filename, which Payload may not have copied onto\n // `data` yet at this point, so fall back to the uploaded file's name.\n data.thumbnailURL = buildThumbnailURL({\n ...data,\n filename: (data as { filename?: string }).filename || file.filename,\n })\n\n return data\n },\n staticHandler: async (req, args) => {\n const requestURL = new URL(req.url || 'http://localhost')\n const tr = requestURL.searchParams.get('tr')\n const hasClientUploadContext = 'clientUploadContext' in args.params\n\n if (hasClientUploadContext) {\n const clientUploadContext = args.params.clientUploadContext\n\n if (!isImageKitClientUploadContext(clientUploadContext)) {\n throw new Error('imagekitStorage: client upload context is invalid.')\n }\n\n const expectedFilePath = getFilePath(\n rootFolder,\n args.params.collection,\n args.params.filename,\n )\n const fileId = clientUploadContext.fileId\n\n if (typeof fileId !== 'string' || !fileId) {\n throw new Error('imagekitStorage: client upload is missing its file ID.')\n }\n\n const uploadedFile = await client.files.get(fileId)\n const uploadedFileMime = uploadedFile.mime\n const uploadedFileSize = uploadedFile.size\n const metadataMatchesUpload =\n uploadedFile.fileId === fileId &&\n uploadedFile.filePath === expectedFilePath &&\n uploadedFile.name === args.params.filename &&\n typeof uploadedFileMime === 'string' &&\n typeof uploadedFile.url === 'string' &&\n typeof uploadedFileSize === 'number' &&\n Number.isSafeInteger(uploadedFileSize) &&\n uploadedFileSize > 0 &&\n Boolean(uploadedFile.isPrivateFile) === isPrivate &&\n (typeof clientUploadContext.size !== 'number' ||\n uploadedFileSize === clientUploadContext.size)\n\n if (\n !metadataMatchesUpload ||\n typeof uploadedFileMime !== 'string' ||\n typeof uploadedFileSize !== 'number'\n ) {\n throw new Error('imagekitStorage: client upload metadata did not match.')\n }\n\n // Replace browser-provided values before hooks persist the upload context.\n Object.assign(clientUploadContext, {\n name: uploadedFile.name,\n fileId: uploadedFile.fileId,\n filePath: uploadedFile.filePath,\n fileType: uploadedFile.fileType,\n mimeType: uploadedFileMime,\n size: uploadedFileSize,\n thumbnailUrl: uploadedFile.thumbnail,\n url: uploadedFile.url,\n })\n\n const verifiedFileURL = isPrivate\n ? buildSignedURL(expectedFilePath, tr)\n : client.helper.buildSrc({\n src: expectedFilePath,\n urlEndpoint,\n })\n\n if (isVideoMimeType(uploadedFileMime)) {\n return fetchUploadedFilePrefix({\n fileSize: uploadedFileSize,\n fileURL: verifiedFileURL,\n mimeType: uploadedFileMime,\n })\n }\n\n return fetchUploadedFile(verifiedFileURL)\n }\n\n const doc =\n args.doc ||\n (await findUploadDocumentByFilename({\n collection: args.params.collection,\n filename: args.params.filename,\n prefix: args.params.prefix,\n req,\n }))\n\n // Sign based on the document's stored status, not the collection's current\n // setting, so files keep working if the collection's privacy is changed later.\n if (isDocumentPrivate(doc, isPrivate)) {\n const mimeType = (doc as { mimeType?: string } | null)?.mimeType\n let signSrc = getFilePath(rootFolder, args.params.collection, args.params.filename)\n\n // A `tr` request for a video/PDF is a thumbnail request (the plain transform\n // can't render a preview); serve ImageKit's generated thumbnail instead.\n if (tr && isVideoMimeType(mimeType)) {\n signSrc = getVideoPosterSrc(signSrc)\n } else if (tr && isPdfMimeType(mimeType)) {\n signSrc = getImageKitThumbnailSrc(signSrc)\n }\n\n return Response.redirect(buildSignedURL(signSrc, tr), 302)\n }\n\n const imageKitURL = client.helper.buildSrc({\n src:\n getStoredImageKitURL(doc) ||\n getFilePath(rootFolder, args.params.collection, args.params.filename),\n transformation: tr ? [{ raw: tr }] : undefined,\n urlEndpoint,\n })\n\n return Response.redirect(imageKitURL, 302)\n },\n }\n }\n}\n\nconst toCloudStorageCollections = (\n collections: ImageKitCollectionMap,\n adapter: Adapter,\n privateSlugs: Set<string>,\n disablePayloadAccessControl?: true,\n): Partial<Record<CollectionSlug, CollectionOptions>> => {\n const mappedCollections: Partial<Record<CollectionSlug, CollectionOptions>> = {}\n\n for (const collectionSlug of Object.keys(collections)) {\n mappedCollections[collectionSlug] = {\n adapter,\n disableLocalStorage: true,\n // Private files require Payload access control so URLs can be signed at serve time.\n disablePayloadAccessControl: privateSlugs.has(collectionSlug)\n ? undefined\n : disablePayloadAccessControl,\n }\n }\n\n return mappedCollections\n}\n\nexport const imagekitStorage =\n (pluginOptions: PayloadStorageImageKitConfig) =>\n (config: Config): Config => {\n const {\n alwaysInsertFields,\n clientUploads,\n collections,\n disablePayloadAccessControl,\n enabled,\n focalPoint,\n folder,\n privateKey,\n publicKey,\n signedUrlExpiresIn: signedUrlExpiresInOption,\n urlEndpoint,\n } = pluginOptions\n\n if (!privateKey?.trim()) {\n throw new Error('imagekitStorage: `privateKey` is required.')\n }\n\n if (!publicKey?.trim()) {\n throw new Error('imagekitStorage: `publicKey` is required.')\n }\n\n if (!urlEndpoint?.trim()) {\n throw new Error('imagekitStorage: `urlEndpoint` is required.')\n }\n\n if (!collections || Object.keys(collections).length === 0) {\n throw new Error('imagekitStorage: `collections` must include at least one upload collection.')\n }\n\n const privateSlugs = new Set(\n Object.entries(collections)\n .filter(([, value]) => isPrivateCollectionConfig(value))\n .map(([slug]) => slug),\n )\n\n if (disablePayloadAccessControl && privateSlugs.size > 0) {\n throw new Error(\n 'imagekitStorage: `disablePayloadAccessControl` cannot be used with private collections. Private files require Payload access control to remain enabled so URLs can be signed at serve time.',\n )\n }\n\n const signedUrlExpiresIn =\n typeof signedUrlExpiresInOption === 'number' && signedUrlExpiresInOption > 0\n ? signedUrlExpiresInOption\n : DEFAULT_SIGNED_URL_EXPIRES_IN\n const apiRoute = config.routes?.api ?? '/api'\n\n const rootFolder = normalizeFolder(folder)\n\n const imageKitClient = new ImageKit({ privateKey })\n const adapter = imagekitAdapter({\n apiRoute,\n client: imageKitClient,\n clientUploads,\n privateSlugs,\n rootFolder,\n signedUrlExpiresIn,\n urlEndpoint,\n })\n\n const configuredSlugs = new Set(Object.keys(collections))\n const configWithCropDisabled: Config = {\n ...config,\n collections: (config.collections || []).map((collection) => {\n if (!configuredSlugs.has(collection.slug)) {\n return collection\n }\n\n const isCollectionPrivate = privateSlugs.has(collection.slug)\n const buildThumbnailURL = (doc: unknown): string =>\n isDocumentPrivate(doc, isCollectionPrivate)\n ? getPrivateAdminThumbnailURL(doc, collection.slug, apiRoute)\n : getAdminThumbnailURL(doc)\n\n const beforeDocumentControls =\n collection.admin?.components?.edit?.beforeDocumentControls || []\n const collectionOptions = collections[collection.slug]\n const collectionFocalPoint =\n collectionOptions !== true &&\n typeof collectionOptions === 'object' &&\n typeof collectionOptions.focalPoint === 'boolean'\n ? collectionOptions.focalPoint\n : undefined\n\n // Surface a \"this is a private collection\" note in the list and edit views.\n // Preserve an existing string description by appending; leave custom\n // component descriptions untouched.\n const existingDescription = collection.admin?.description\n const baseAdmin = isCollectionPrivate\n ? {\n ...(collection.admin || {}),\n description:\n typeof existingDescription === 'string' && existingDescription.trim().length > 0\n ? `${existingDescription} ${PRIVATE_COLLECTION_DESCRIPTION}`\n : (existingDescription ?? PRIVATE_COLLECTION_DESCRIPTION),\n }\n : collection.admin\n\n return {\n ...collection,\n admin: clientUploads\n ? {\n ...(baseAdmin || {}),\n components: {\n ...(collection.admin?.components || {}),\n edit: {\n ...(collection.admin?.components?.edit || {}),\n beforeDocumentControls: [\n ...beforeDocumentControls,\n {\n clientProps: {\n authPath: getImageKitUploadAuthPath(collection.slug),\n collectionSlug: collection.slug,\n folder: getCollectionFolder(rootFolder, collection.slug),\n isPrivate: isCollectionPrivate,\n publicKey,\n },\n path: '@humaan/payload-storage-imagekit/client#ImageKitUploadHandler',\n },\n ],\n },\n },\n }\n : baseAdmin,\n hooks: {\n ...(collection.hooks || {}),\n beforeChange: [\n ...(collection.hooks?.beforeChange || []),\n ({ data, req }) => {\n if (!isRecord(data) || !req.file?.clientUploadContext) {\n return data\n }\n\n return applyClientUploadContextToData(\n data,\n req.file.clientUploadContext,\n buildThumbnailURL,\n isCollectionPrivate,\n )\n },\n ],\n },\n upload: {\n // Focal points are opt-in for configured collections. Plugin-level\n // and collection-level options can override this default; a\n // collection's own `upload.focalPoint` still wins because it is\n // spread in afterwards.\n focalPoint: false,\n ...(typeof focalPoint === 'boolean' ? { focalPoint } : {}),\n ...(typeof collectionFocalPoint === 'boolean'\n ? { focalPoint: collectionFocalPoint }\n : {}),\n ...(typeof collection.upload === 'object' ? collection.upload : {}),\n adminThumbnail: ({ doc }) => buildThumbnailURL(doc),\n crop: false,\n },\n }\n }),\n endpoints: clientUploads\n ? [\n ...(config.endpoints || []),\n {\n handler: createImageKitUploadAuthHandler({\n client: imageKitClient,\n collections,\n }),\n method: 'get',\n path: `${getImageKitUploadAuthPath(':collection')}`,\n },\n ]\n : config.endpoints,\n }\n\n return cloudStoragePlugin({\n alwaysInsertFields,\n collections: toCloudStorageCollections(\n collections,\n adapter,\n privateSlugs,\n disablePayloadAccessControl,\n ),\n enabled,\n })(configWithCropDisabled)\n }\n\nexport { migrateFolder } from './migrate.js'\nexport type { MigrateFolderOptions, MigrationJobResult } from './migrate.js'\n\n// Backward-compatible alias\nexport const payloadStorageImagekit = imagekitStorage\nexport const payloadStorageImageKit = imagekitStorage\n\nexport type PayloadStorageImagekitConfig = PayloadStorageImageKitConfig\n"],"names":["ImageKit","toFile","cloudStoragePlugin","path","getImageKitUploadAuthPath","isImageKitClientUploadContext","createImageKitUploadAuthHandler","getAdminThumbnailURL","getPrivateAdminThumbnailURL","isPdfMimeType","isVideoMimeType","getImageKitThumbnailSrc","getVideoPosterSrc","DEFAULT_SIGNED_URL_EXPIRES_IN","FILE_TYPE_DETECTION_SIZE","FILE_TYPE_DETECTION_RANGE","PRIVATE_COLLECTION_DESCRIPTION","isPrivateCollectionConfig","value","private","isDocumentPrivate","doc","collectionIsPrivate","stored","imagekit","isPrivate","normalizeFolder","folder","normalized","trim","replace","Error","getCollectionFolder","rootFolder","collectionSlug","posix","join","getFilePath","filename","findImageKitFileId","filePath","parentPath","dirname","name","basename","limit","skip","assets","list","type","found","find","asset","fileId","length","undefined","isRecord","getStoredImageKitURL","storedURL","url","hasStoredImageKitURL","findUploadDocumentByFilename","collection","prefix","req","constraints","equals","push","payload","db","findOne","where","and","applyClientUploadContextToData","data","clientUploadContext","buildThumbnailURL","thumbnailUrl","thumbnailURL","imagekitAdapter","apiRoute","client","clientUploads","privateSlugs","signedUrlExpiresIn","urlEndpoint","fetchUploadedFile","fileURL","range","response","fetch","headers","Range","ok","status","fetchUploadedFilePrefix","fileSize","mimeType","contentLength","Number","get","contentRange","rangeMatch","match","rangeEnd","NaN","totalSize","expectedLength","Math","min","isSafeInteger","body","cancel","filePrefix","arrayBuffer","byteLength","Response","String","has","slug","buildSignedURL","src","tr","helper","buildSrc","expiresIn","signed","transformation","raw","fields","admin","condition","disableListColumn","disableListFilter","hidden","readOnly","label","_data","siblingData","description","hooks","afterRead","virtual","interfaceName","generateURL","handleDelete","storedFileId","files","delete","handleUpload","file","uploadFile","buffer","uploaded","upload","fileName","useUniqueFileName","isPrivateFile","staticHandler","args","requestURL","URL","searchParams","hasClientUploadContext","params","expectedFilePath","uploadedFile","uploadedFileMime","mime","uploadedFileSize","size","metadataMatchesUpload","Boolean","Object","assign","fileType","thumbnail","verifiedFileURL","signSrc","redirect","imageKitURL","toCloudStorageCollections","collections","adapter","disablePayloadAccessControl","mappedCollections","keys","disableLocalStorage","imagekitStorage","pluginOptions","config","alwaysInsertFields","enabled","focalPoint","privateKey","publicKey","signedUrlExpiresInOption","Set","entries","filter","map","routes","api","imageKitClient","configuredSlugs","configWithCropDisabled","isCollectionPrivate","beforeDocumentControls","components","edit","collectionOptions","collectionFocalPoint","existingDescription","baseAdmin","clientProps","authPath","beforeChange","adminThumbnail","crop","endpoints","handler","method","migrateFolder","payloadStorageImagekit","payloadStorageImageKit"],"mappings":"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"}
package/dist/migrate.js CHANGED
@@ -1,4 +1,4 @@
1
- import ImageKit from '@imagekit/nodejs';
1
+ /* eslint-disable no-console -- Migration progress is intentionally reported to the invoking process. */ import ImageKit from '@imagekit/nodejs';
2
2
  import path from 'node:path';
3
3
  const DEFAULT_SIGNED_URL_EXPIRES_IN = 900;
4
4
  const isPrivateCollectionConfig = (value)=>value !== true && typeof value === 'object' && value?.private === true;
@@ -83,7 +83,7 @@ const uploadFileFromURL = async ({ client, fileName, folder, isPrivate, sourceUr
83
83
  url: uploaded.url
84
84
  };
85
85
  };
86
- const getSourceImagekitFields = async ({ client, fileId })=>{
86
+ const getSourceImageKitFields = async ({ client, fileId })=>{
87
87
  const file = await client.files.get(fileId);
88
88
  if (!file.fileId || !file.filePath || !file.url) {
89
89
  throw new Error(`migrateFolder: missing ImageKit source file details for "${fileId}".`);
@@ -95,7 +95,7 @@ const getSourceImagekitFields = async ({ client, fileId })=>{
95
95
  url: file.url
96
96
  };
97
97
  };
98
- const getUploadedImagekitFields = async ({ client, fileId })=>{
98
+ const getUploadedImageKitFields = async ({ client, fileId })=>{
99
99
  const file = await client.files.get(fileId);
100
100
  if (!file.fileId || !file.url) {
101
101
  throw new Error(`migrateFolder: missing ImageKit file details for "${fileId}".`);
@@ -179,20 +179,20 @@ export const migrateFolder = async (options)=>{
179
179
  for(let i = 0; i < found.docs.length; i++){
180
180
  const doc = found.docs[i];
181
181
  const filename = doc.filename;
182
- const existingImagekit = doc.imagekit;
183
- const displayName = filename || existingImagekit?.fileId || doc.id;
182
+ const existingImageKit = doc.imagekit;
183
+ const displayName = filename || existingImageKit?.fileId || doc.id;
184
184
  console.log(`[migrateFolder] [${i + 1}/${found.docs.length}] Processing document ${doc.id}`);
185
185
  // Skip items that don't have an ImageKit file ID to migrate from.
186
- if (!existingImagekit?.fileId) {
186
+ if (!existingImageKit?.fileId) {
187
187
  skippedCount += 1;
188
188
  console.log(`[migrateFolder] Skipping document ${doc.id} - missing ImageKit file ID`);
189
189
  continue;
190
190
  }
191
191
  try {
192
192
  const destinationFolder = getCollectionFolder(toFolder, slug);
193
- const source = await getSourceImagekitFields({
193
+ const source = await getSourceImageKitFields({
194
194
  client,
195
- fileId: existingImagekit.fileId
195
+ fileId: existingImageKit.fileId
196
196
  });
197
197
  if (isFileInFolder(source.filePath, destinationFolder)) {
198
198
  skippedCount += 1;
@@ -220,7 +220,7 @@ export const migrateFolder = async (options)=>{
220
220
  if (!copiedFileId) {
221
221
  throw new Error(`migrateFolder: copy request completed for ImageKit file ID "${source.fileId}" from "${source.filePath}", but the destination asset at "${destinationFilePath}" was not discoverable via ImageKit after retrying.`);
222
222
  }
223
- uploaded = await getUploadedImagekitFields({
223
+ uploaded = await getUploadedImageKitFields({
224
224
  client,
225
225
  fileId: copiedFileId
226
226
  });