@muhgholy/next-drive 4.7.0 → 4.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -370,75 +370,77 @@ var extractImageMetadata = async (filePath) => {
370
370
  }
371
371
  };
372
372
  var DISPLAY_PRESETS = {
373
- "article-header": 0.9,
374
- // Hero/banner images - high quality
375
- "article-image": 0.85,
376
- // In-content images
377
- "thumbnail": 0.7,
378
- // Small previews - lower quality ok
379
- "avatar": 0.8,
380
- // Profile pictures
381
- "logo": 0.95,
382
- // Branding - needs clarity
383
- "card": 0.8,
384
- // Card components
385
- "gallery": 0.85,
386
- // Gallery/grid images
387
- "og": 0.9,
388
- // Open Graph/social sharing
389
- "icon": 0.75,
390
- // Small icons
391
- "cover": 0.9,
392
- // Full-width covers
393
- "story": 0.85
394
- // Story/vertical format
373
+ "article-header": { ratio: [16, 9], baseWidth: 1200, qualityFactor: 0.9, defaultFit: "inside" },
374
+ "article-image": { ratio: [16, 9], baseWidth: 800, qualityFactor: 0.85, defaultFit: "inside" },
375
+ "thumbnail": { ratio: [1, 1], baseWidth: 150, qualityFactor: 0.7, defaultFit: "cover" },
376
+ "avatar": { ratio: [1, 1], baseWidth: 128, qualityFactor: 0.8, defaultFit: "cover" },
377
+ "logo": { ratio: [2, 1], baseWidth: 200, qualityFactor: 0.95, defaultFit: "contain" },
378
+ "card": { ratio: [4, 3], baseWidth: 400, qualityFactor: 0.8, defaultFit: "cover" },
379
+ "gallery": { ratio: [1, 1], baseWidth: 600, qualityFactor: 0.85, defaultFit: "cover" },
380
+ "og": { ratio: [1200, 630], baseWidth: 1200, qualityFactor: 0.9, defaultFit: "cover" },
381
+ "icon": { ratio: [1, 1], baseWidth: 48, qualityFactor: 0.75, defaultFit: "cover" },
382
+ "cover": { ratio: [16, 9], baseWidth: 1920, qualityFactor: 0.9, defaultFit: "cover" },
383
+ "story": { ratio: [9, 16], baseWidth: 1080, qualityFactor: 0.85, defaultFit: "cover" },
384
+ "video": { ratio: [16, 9], baseWidth: 1280, qualityFactor: 0.85, defaultFit: "cover" },
385
+ "banner": { ratio: [3, 1], baseWidth: 1200, qualityFactor: 0.9, defaultFit: "cover" },
386
+ "portrait": { ratio: [3, 4], baseWidth: 600, qualityFactor: 0.85, defaultFit: "inside" },
387
+ "landscape": { ratio: [4, 3], baseWidth: 800, qualityFactor: 0.85, defaultFit: "inside" }
395
388
  };
396
- var SIZE_PRESETS = {
397
- // Square sizes
389
+ var VALID_FIT_OPTIONS = ["cover", "contain", "fill", "inside", "outside"];
390
+ var VALID_POSITION_OPTIONS = [
391
+ "center",
392
+ "top",
393
+ "right top",
394
+ "right",
395
+ "right bottom",
396
+ "bottom",
397
+ "left bottom",
398
+ "left",
399
+ "left top",
400
+ "attention",
401
+ "entropy"
402
+ ];
403
+ var SIZE_SCALES = {
404
+ "xs": 0.25,
405
+ "sm": 0.5,
406
+ "md": 1,
407
+ "lg": 1.5,
408
+ "xl": 2,
409
+ "2xl": 2.5
410
+ };
411
+ var STANDALONE_SIZES = {
398
412
  "xs": { width: 64, height: 64 },
399
413
  "sm": { width: 128, height: 128 },
400
414
  "md": { width: 256, height: 256 },
401
415
  "lg": { width: 512, height: 512 },
402
416
  "xl": { width: 1024, height: 1024 },
403
417
  "2xl": { width: 1600, height: 1600 },
404
- // Named squares
405
418
  "icon": { width: 48, height: 48 },
406
419
  "thumb": { width: 150, height: 150 },
407
420
  "square": { width: 600, height: 600 },
408
421
  "avatar-sm": { width: 64, height: 64 },
409
422
  "avatar-md": { width: 128, height: 128 },
410
423
  "avatar-lg": { width: 256, height: 256 },
411
- // Landscape (16:9)
412
424
  "landscape-sm": { width: 480, height: 270 },
413
425
  "landscape": { width: 800, height: 450 },
414
426
  "landscape-lg": { width: 1280, height: 720 },
415
427
  "landscape-xl": { width: 1920, height: 1080 },
416
- // Portrait (9:16)
417
428
  "portrait-sm": { width: 270, height: 480 },
418
429
  "portrait": { width: 450, height: 800 },
419
430
  "portrait-lg": { width: 720, height: 1280 },
420
- // Wide/Banner (OG, social)
421
431
  "wide": { width: 1200, height: 630 },
422
- // Open Graph standard
423
432
  "banner": { width: 1200, height: 400 },
424
- // Banner/header
425
433
  "banner-sm": { width: 800, height: 200 },
426
- // Classic photo ratios
427
434
  "photo-4x3": { width: 800, height: 600 },
428
- // 4:3
429
435
  "photo-3x2": { width: 900, height: 600 },
430
- // 3:2
431
- // Story/vertical (9:16)
432
436
  "story": { width: 1080, height: 1920 },
433
- // Video thumbnails
434
437
  "video": { width: 1280, height: 720 },
435
438
  "video-sm": { width: 640, height: 360 },
436
- // Card sizes
437
439
  "card-sm": { width: 300, height: 200 },
438
440
  "card": { width: 400, height: 300 },
439
441
  "card-lg": { width: 600, height: 400 }
440
442
  };
441
- var getImageSettings = (fileSizeInBytes, qualityPreset, display, size) => {
443
+ var getImageSettings = (fileSizeInBytes, qualityPreset, display, size, fit, position) => {
442
444
  let baseQuality = 80;
443
445
  if (qualityPreset === "low") baseQuality = 30;
444
446
  else if (qualityPreset === "medium") baseQuality = 50;
@@ -447,8 +449,28 @@ var getImageSettings = (fileSizeInBytes, qualityPreset, display, size) => {
447
449
  const n = parseInt(qualityPreset, 10);
448
450
  if (!isNaN(n)) baseQuality = Math.min(100, Math.max(1, n));
449
451
  }
450
- const displayFactor = display && DISPLAY_PRESETS[display] ? DISPLAY_PRESETS[display] : 1;
451
- baseQuality = Math.round(baseQuality * displayFactor);
452
+ let width;
453
+ let height;
454
+ let qualityFactor = 1;
455
+ let defaultFit = "inside";
456
+ const displayPreset = display ? DISPLAY_PRESETS[display] : void 0;
457
+ if (displayPreset) {
458
+ qualityFactor = displayPreset.qualityFactor;
459
+ defaultFit = displayPreset.defaultFit;
460
+ const [ratioW, ratioH] = displayPreset.ratio;
461
+ const scale = size && SIZE_SCALES[size] ? SIZE_SCALES[size] : 1;
462
+ width = Math.round(displayPreset.baseWidth * scale);
463
+ height = Math.round(width * ratioH / ratioW);
464
+ } else if (size) {
465
+ const standalone = STANDALONE_SIZES[size];
466
+ if (standalone) {
467
+ width = standalone.width;
468
+ height = standalone.height;
469
+ }
470
+ }
471
+ const resolvedFit = fit && VALID_FIT_OPTIONS.includes(fit) ? fit : defaultFit;
472
+ const resolvedPosition = position && VALID_POSITION_OPTIONS.includes(position) ? position : void 0;
473
+ baseQuality = Math.round(baseQuality * qualityFactor);
452
474
  let quality = baseQuality;
453
475
  let effort = 4;
454
476
  let pngCompression = 6;
@@ -476,12 +498,12 @@ var getImageSettings = (fileSizeInBytes, qualityPreset, display, size) => {
476
498
  pngCompression = 7;
477
499
  }
478
500
  }
479
- const dimensions = size && SIZE_PRESETS[size] ? SIZE_PRESETS[size] : void 0;
480
501
  return {
481
502
  quality: Math.max(1, Math.min(100, quality)),
482
503
  effort,
483
504
  pngCompression,
484
- ...dimensions && { width: dimensions.width, height: dimensions.height }
505
+ ...width && height && { width, height, fit: resolvedFit },
506
+ ...resolvedPosition && { position: resolvedPosition }
485
507
  };
486
508
  };
487
509
  var objectIdSchema = zod.z.string().refine((val) => mongoose.isValidObjectId(val), {
@@ -1560,9 +1582,9 @@ var driveUpload = async (source, key, options) => {
1560
1582
  }
1561
1583
  }
1562
1584
  let resolvedParentId = null;
1563
- if (options.folder?.path) {
1585
+ if (options.folder && "path" in options.folder) {
1564
1586
  resolvedParentId = await resolveFolderByPath(options.folder.path, key, accountId);
1565
- } else if (options.folder?.id && options.folder.id !== "root") {
1587
+ } else if (options.folder && "id" in options.folder && options.folder.id !== "root") {
1566
1588
  resolvedParentId = options.folder.id;
1567
1589
  } else if (options.parentId && options.parentId !== "root") {
1568
1590
  resolvedParentId = options.parentId;
@@ -1722,23 +1744,30 @@ var driveAPIHandler = async (req, res) => {
1722
1744
  const quality = req.query.quality;
1723
1745
  const display = req.query.display;
1724
1746
  const sizePreset = req.query.size;
1747
+ const fit = req.query.fit;
1748
+ const position = req.query.position;
1725
1749
  const isImage = mime.startsWith("image/");
1726
- const shouldTransform = isImage && (format || quality || display || sizePreset);
1750
+ const shouldTransform = isImage && (format || quality || display || sizePreset || fit);
1727
1751
  res.setHeader("Content-Disposition", `inline; filename="${safeFilename}"`);
1728
1752
  if (config.cors?.enabled) {
1729
1753
  res.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
1730
1754
  }
1731
1755
  if (shouldTransform) {
1732
1756
  try {
1733
- const settings = getImageSettings(fileSize, quality, display, sizePreset);
1757
+ const settings = getImageSettings(fileSize, quality, display, sizePreset, fit, position);
1734
1758
  let targetFormat = format || mime.split("/")[1];
1735
1759
  if (targetFormat === "jpg") targetFormat = "jpeg";
1760
+ if (!["jpeg", "png", "webp", "avif"].includes(targetFormat)) {
1761
+ targetFormat = format || "webp";
1762
+ }
1736
1763
  const cacheDir = path__default.default.join(config.storage.path, "file", drive._id.toString(), "cache");
1737
1764
  const cacheKey = [
1738
1765
  "opt",
1739
1766
  `q${settings.quality}`,
1740
1767
  `e${settings.effort}`,
1741
1768
  settings.width ? `${settings.width}x${settings.height}` : "orig",
1769
+ settings.fit || "none",
1770
+ settings.position || "c",
1742
1771
  targetFormat
1743
1772
  ].join("_");
1744
1773
  const cachePath = path__default.default.join(cacheDir, `${cacheKey}.bin`);
@@ -1758,7 +1787,8 @@ var driveAPIHandler = async (req, res) => {
1758
1787
  let pipeline = sharp__default.default();
1759
1788
  if (settings.width && settings.height) {
1760
1789
  pipeline = pipeline.resize(settings.width, settings.height, {
1761
- fit: "inside",
1790
+ fit: settings.fit || "inside",
1791
+ position: settings.position || "center",
1762
1792
  withoutEnlargement: true
1763
1793
  });
1764
1794
  }
@@ -1769,13 +1799,17 @@ var driveAPIHandler = async (req, res) => {
1769
1799
  pipeline = pipeline.png({ compressionLevel: settings.pngCompression, adaptiveFiltering: true });
1770
1800
  res.setHeader("Content-Type", "image/png");
1771
1801
  } else if (targetFormat === "webp") {
1772
- pipeline = pipeline.webp({ quality: settings.quality, effort: settings.effort });
1802
+ const webpEffort = Math.min(settings.effort, 6);
1803
+ pipeline = pipeline.webp({ quality: settings.quality, effort: webpEffort });
1773
1804
  res.setHeader("Content-Type", "image/webp");
1774
1805
  } else if (targetFormat === "avif") {
1775
1806
  pipeline = pipeline.avif({ quality: settings.quality, effort: settings.effort });
1776
1807
  res.setHeader("Content-Type", "image/avif");
1777
1808
  }
1778
1809
  res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
1810
+ pipeline.on("error", (err) => {
1811
+ console.error("[next-drive] Pipeline error:", err);
1812
+ });
1779
1813
  stream.pipe(pipeline);
1780
1814
  pipeline.clone().toFile(cachePath).catch((e) => console.error("[next-drive] Cache write failed:", e));
1781
1815
  pipeline.clone().pipe(res);
@@ -2330,5 +2364,5 @@ exports.driveReadFile = driveReadFile;
2330
2364
  exports.driveUpload = driveUpload;
2331
2365
  exports.getDriveConfig = getDriveConfig;
2332
2366
  exports.getDriveInformation = getDriveInformation;
2333
- //# sourceMappingURL=chunk-YR4DEKWI.cjs.map
2334
- //# sourceMappingURL=chunk-YR4DEKWI.cjs.map
2367
+ //# sourceMappingURL=chunk-WZWJYN64.cjs.map
2368
+ //# sourceMappingURL=chunk-WZWJYN64.cjs.map