@muhgholy/next-drive 4.3.0 → 4.5.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.
@@ -68,6 +68,24 @@ var drive_default = Drive;
68
68
  // src/server/utils/migration.ts
69
69
  var MIGRATION_FILE = ".migration-version";
70
70
  var CURRENT_VERSION = 1;
71
+ var isReadyForMigration = () => {
72
+ if (mongoose.connection.readyState !== 1) {
73
+ console.warn("[next-drive] Migration skipped: Database not connected");
74
+ return false;
75
+ }
76
+ return true;
77
+ };
78
+ var isNewInstallation = (storagePath) => {
79
+ if (!fs.existsSync(storagePath)) return true;
80
+ const hasOldDriveDir = fs.existsSync(path.join(storagePath, "drive"));
81
+ const hasOldCacheDir = fs.existsSync(path.join(storagePath, "cache", "thumbnails"));
82
+ const hasOldLibraryDir = fs.existsSync(path.join(storagePath, "library", "google"));
83
+ const hasRootLevelFiles = fs.existsSync(storagePath) && fs.readdirSync(storagePath).some((entry) => {
84
+ const entryPath = path.join(storagePath, entry);
85
+ return fs.statSync(entryPath).isDirectory() && /^[a-f0-9]{24}$/i.test(entry) && entry !== "file";
86
+ });
87
+ return !hasOldDriveDir && !hasOldCacheDir && !hasOldLibraryDir && !hasRootLevelFiles;
88
+ };
71
89
  var migrations = [
72
90
  {
73
91
  version: 1,
@@ -185,6 +203,12 @@ var migrations = [
185
203
  }
186
204
  ];
187
205
  var runMigrations = async (storagePath) => {
206
+ if (!isReadyForMigration()) {
207
+ return;
208
+ }
209
+ if (!fs.existsSync(storagePath)) {
210
+ fs.mkdirSync(storagePath, { recursive: true });
211
+ }
188
212
  const versionFile = path.join(storagePath, MIGRATION_FILE);
189
213
  let currentVersion = 0;
190
214
  if (fs.existsSync(versionFile)) {
@@ -195,8 +219,10 @@ var runMigrations = async (storagePath) => {
195
219
  }
196
220
  }
197
221
  if (currentVersion >= CURRENT_VERSION) return;
198
- if (!fs.existsSync(storagePath)) {
199
- fs.mkdirSync(storagePath, { recursive: true });
222
+ if (isNewInstallation(storagePath)) {
223
+ console.log("[next-drive] New installation detected, skipping migration");
224
+ fs.writeFileSync(versionFile, String(CURRENT_VERSION));
225
+ return;
200
226
  }
201
227
  const pendingMigrations = migrations.filter((m) => m.version > currentVersion);
202
228
  for (const migration of pendingMigrations.sort((a, b) => a.version - b.version)) {
@@ -213,14 +239,15 @@ var runMigrations = async (storagePath) => {
213
239
 
214
240
  // src/server/config.ts
215
241
  var globalConfig = null;
216
- var migrationRun = false;
242
+ var migrationPromise = null;
243
+ var configInitialized = false;
217
244
  var driveConfiguration = async (config) => {
218
245
  if (mongoose.connection.readyState !== 1) {
219
246
  throw new Error("Database not connected. Please connect to Mongoose before initializing next-drive.");
220
247
  }
221
- if (!migrationRun) {
222
- await runMigrations(config.storage.path);
223
- migrationRun = true;
248
+ if (configInitialized && globalConfig) {
249
+ if (migrationPromise) await migrationPromise;
250
+ return globalConfig;
224
251
  }
225
252
  const mode = config.mode || "NORMAL";
226
253
  if (mode === "ROOT") {
@@ -233,7 +260,6 @@ var driveConfiguration = async (config) => {
233
260
  allowedMimeTypes: ["*/*"]
234
261
  }
235
262
  };
236
- return globalConfig;
237
263
  } else {
238
264
  if (!config.information) {
239
265
  throw new Error("information callback is required in NORMAL mode");
@@ -249,8 +275,13 @@ var driveConfiguration = async (config) => {
249
275
  },
250
276
  information: config.information
251
277
  };
252
- return globalConfig;
253
278
  }
279
+ configInitialized = true;
280
+ if (!migrationPromise) {
281
+ migrationPromise = runMigrations(config.storage.path);
282
+ }
283
+ await migrationPromise;
284
+ return globalConfig;
254
285
  };
255
286
  var getDriveConfig = () => {
256
287
  if (!globalConfig) throw new Error("Drive configuration not initialized");
@@ -325,6 +356,14 @@ var extractImageMetadata = async (filePath) => {
325
356
  return null;
326
357
  }
327
358
  };
359
+ var parseQuality = (q) => {
360
+ if (!q) return 80;
361
+ if (q === "low") return 40;
362
+ if (q === "medium") return 60;
363
+ if (q === "high") return 80;
364
+ const n = parseInt(q, 10);
365
+ return isNaN(n) ? 80 : Math.min(100, Math.max(1, n));
366
+ };
328
367
  var objectIdSchema = z.string().refine((val) => isValidObjectId(val), {
329
368
  message: "Invalid ObjectId format"
330
369
  });
@@ -1511,11 +1550,59 @@ var driveAPIHandler = async (req, res) => {
1511
1550
  if (action === "serve") {
1512
1551
  const { stream, mime, size } = await itemProvider.openStream(drive, itemAccountId);
1513
1552
  const safeFilename = sanitizeContentDispositionFilename(drive.name);
1553
+ const format = req.query.format;
1554
+ const quality = req.query.quality;
1555
+ const isImage = mime.startsWith("image/");
1556
+ const shouldTransform = isImage && (format || quality);
1514
1557
  res.setHeader("Content-Disposition", `inline; filename="${safeFilename}"`);
1515
- res.setHeader("Content-Type", mime);
1516
1558
  if (config.cors?.enabled) {
1517
1559
  res.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
1518
1560
  }
1561
+ if (shouldTransform) {
1562
+ try {
1563
+ const qValue = parseQuality(quality);
1564
+ let targetFormat = format || mime.split("/")[1];
1565
+ if (targetFormat === "jpg") targetFormat = "jpeg";
1566
+ const cacheDir = path.join(config.storage.path, "file", drive._id.toString(), "cache");
1567
+ const cacheFilename = `optimized_q${qValue}_${targetFormat}.bin`;
1568
+ const cachePath = path.join(cacheDir, cacheFilename);
1569
+ if (fs.existsSync(cachePath)) {
1570
+ const cacheStat = fs.statSync(cachePath);
1571
+ res.setHeader("Content-Type", `image/${targetFormat}`);
1572
+ res.setHeader("Content-Length", cacheStat.size);
1573
+ res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
1574
+ if (config.cors?.enabled) {
1575
+ res.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
1576
+ }
1577
+ if ("destroy" in stream) stream.destroy();
1578
+ fs.createReadStream(cachePath).pipe(res);
1579
+ return;
1580
+ }
1581
+ if (!fs.existsSync(cacheDir)) fs.mkdirSync(cacheDir, { recursive: true });
1582
+ const pipeline = sharp();
1583
+ if (targetFormat === "jpeg") {
1584
+ pipeline.jpeg({ quality: qValue, mozjpeg: true });
1585
+ res.setHeader("Content-Type", "image/jpeg");
1586
+ } else if (targetFormat === "png") {
1587
+ pipeline.png({ quality: qValue });
1588
+ res.setHeader("Content-Type", "image/png");
1589
+ } else if (targetFormat === "webp") {
1590
+ pipeline.webp({ quality: qValue });
1591
+ res.setHeader("Content-Type", "image/webp");
1592
+ } else if (targetFormat === "avif") {
1593
+ pipeline.avif({ quality: qValue });
1594
+ res.setHeader("Content-Type", "image/avif");
1595
+ }
1596
+ res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
1597
+ stream.pipe(pipeline);
1598
+ pipeline.clone().toFile(cachePath).catch((e) => console.error("[next-drive] Cache write failed:", e));
1599
+ pipeline.clone().pipe(res);
1600
+ return;
1601
+ } catch (e) {
1602
+ console.error("[next-drive] Image transformation failed:", e);
1603
+ }
1604
+ }
1605
+ res.setHeader("Content-Type", mime);
1519
1606
  if (size) res.setHeader("Content-Length", size);
1520
1607
  stream.pipe(res);
1521
1608
  return;
@@ -2050,5 +2137,5 @@ var driveAPIHandler = async (req, res) => {
2050
2137
  };
2051
2138
 
2052
2139
  export { driveAPIHandler, driveConfiguration, driveDelete, driveFilePath, driveFileSchemaZod, driveGetUrl, driveInfo, driveList, driveReadFile, driveUpload, getDriveConfig, getDriveInformation };
2053
- //# sourceMappingURL=chunk-MTGJTRD5.js.map
2054
- //# sourceMappingURL=chunk-MTGJTRD5.js.map
2140
+ //# sourceMappingURL=chunk-YUU5BFE7.js.map
2141
+ //# sourceMappingURL=chunk-YUU5BFE7.js.map