@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.
@@ -81,6 +81,24 @@ var drive_default = Drive;
81
81
  // src/server/utils/migration.ts
82
82
  var MIGRATION_FILE = ".migration-version";
83
83
  var CURRENT_VERSION = 1;
84
+ var isReadyForMigration = () => {
85
+ if (mongoose__default.default.connection.readyState !== 1) {
86
+ console.warn("[next-drive] Migration skipped: Database not connected");
87
+ return false;
88
+ }
89
+ return true;
90
+ };
91
+ var isNewInstallation = (storagePath) => {
92
+ if (!fs__default.default.existsSync(storagePath)) return true;
93
+ const hasOldDriveDir = fs__default.default.existsSync(path__default.default.join(storagePath, "drive"));
94
+ const hasOldCacheDir = fs__default.default.existsSync(path__default.default.join(storagePath, "cache", "thumbnails"));
95
+ const hasOldLibraryDir = fs__default.default.existsSync(path__default.default.join(storagePath, "library", "google"));
96
+ const hasRootLevelFiles = fs__default.default.existsSync(storagePath) && fs__default.default.readdirSync(storagePath).some((entry) => {
97
+ const entryPath = path__default.default.join(storagePath, entry);
98
+ return fs__default.default.statSync(entryPath).isDirectory() && /^[a-f0-9]{24}$/i.test(entry) && entry !== "file";
99
+ });
100
+ return !hasOldDriveDir && !hasOldCacheDir && !hasOldLibraryDir && !hasRootLevelFiles;
101
+ };
84
102
  var migrations = [
85
103
  {
86
104
  version: 1,
@@ -198,6 +216,12 @@ var migrations = [
198
216
  }
199
217
  ];
200
218
  var runMigrations = async (storagePath) => {
219
+ if (!isReadyForMigration()) {
220
+ return;
221
+ }
222
+ if (!fs__default.default.existsSync(storagePath)) {
223
+ fs__default.default.mkdirSync(storagePath, { recursive: true });
224
+ }
201
225
  const versionFile = path__default.default.join(storagePath, MIGRATION_FILE);
202
226
  let currentVersion = 0;
203
227
  if (fs__default.default.existsSync(versionFile)) {
@@ -208,8 +232,10 @@ var runMigrations = async (storagePath) => {
208
232
  }
209
233
  }
210
234
  if (currentVersion >= CURRENT_VERSION) return;
211
- if (!fs__default.default.existsSync(storagePath)) {
212
- fs__default.default.mkdirSync(storagePath, { recursive: true });
235
+ if (isNewInstallation(storagePath)) {
236
+ console.log("[next-drive] New installation detected, skipping migration");
237
+ fs__default.default.writeFileSync(versionFile, String(CURRENT_VERSION));
238
+ return;
213
239
  }
214
240
  const pendingMigrations = migrations.filter((m) => m.version > currentVersion);
215
241
  for (const migration of pendingMigrations.sort((a, b) => a.version - b.version)) {
@@ -226,14 +252,15 @@ var runMigrations = async (storagePath) => {
226
252
 
227
253
  // src/server/config.ts
228
254
  var globalConfig = null;
229
- var migrationRun = false;
255
+ var migrationPromise = null;
256
+ var configInitialized = false;
230
257
  var driveConfiguration = async (config) => {
231
258
  if (mongoose__default.default.connection.readyState !== 1) {
232
259
  throw new Error("Database not connected. Please connect to Mongoose before initializing next-drive.");
233
260
  }
234
- if (!migrationRun) {
235
- await runMigrations(config.storage.path);
236
- migrationRun = true;
261
+ if (configInitialized && globalConfig) {
262
+ if (migrationPromise) await migrationPromise;
263
+ return globalConfig;
237
264
  }
238
265
  const mode = config.mode || "NORMAL";
239
266
  if (mode === "ROOT") {
@@ -246,7 +273,6 @@ var driveConfiguration = async (config) => {
246
273
  allowedMimeTypes: ["*/*"]
247
274
  }
248
275
  };
249
- return globalConfig;
250
276
  } else {
251
277
  if (!config.information) {
252
278
  throw new Error("information callback is required in NORMAL mode");
@@ -262,8 +288,13 @@ var driveConfiguration = async (config) => {
262
288
  },
263
289
  information: config.information
264
290
  };
265
- return globalConfig;
266
291
  }
292
+ configInitialized = true;
293
+ if (!migrationPromise) {
294
+ migrationPromise = runMigrations(config.storage.path);
295
+ }
296
+ await migrationPromise;
297
+ return globalConfig;
267
298
  };
268
299
  var getDriveConfig = () => {
269
300
  if (!globalConfig) throw new Error("Drive configuration not initialized");
@@ -338,6 +369,14 @@ var extractImageMetadata = async (filePath) => {
338
369
  return null;
339
370
  }
340
371
  };
372
+ var parseQuality = (q) => {
373
+ if (!q) return 80;
374
+ if (q === "low") return 40;
375
+ if (q === "medium") return 60;
376
+ if (q === "high") return 80;
377
+ const n = parseInt(q, 10);
378
+ return isNaN(n) ? 80 : Math.min(100, Math.max(1, n));
379
+ };
341
380
  var objectIdSchema = zod.z.string().refine((val) => mongoose.isValidObjectId(val), {
342
381
  message: "Invalid ObjectId format"
343
382
  });
@@ -1524,11 +1563,59 @@ var driveAPIHandler = async (req, res) => {
1524
1563
  if (action === "serve") {
1525
1564
  const { stream, mime, size } = await itemProvider.openStream(drive, itemAccountId);
1526
1565
  const safeFilename = sanitizeContentDispositionFilename(drive.name);
1566
+ const format = req.query.format;
1567
+ const quality = req.query.quality;
1568
+ const isImage = mime.startsWith("image/");
1569
+ const shouldTransform = isImage && (format || quality);
1527
1570
  res.setHeader("Content-Disposition", `inline; filename="${safeFilename}"`);
1528
- res.setHeader("Content-Type", mime);
1529
1571
  if (config.cors?.enabled) {
1530
1572
  res.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
1531
1573
  }
1574
+ if (shouldTransform) {
1575
+ try {
1576
+ const qValue = parseQuality(quality);
1577
+ let targetFormat = format || mime.split("/")[1];
1578
+ if (targetFormat === "jpg") targetFormat = "jpeg";
1579
+ const cacheDir = path__default.default.join(config.storage.path, "file", drive._id.toString(), "cache");
1580
+ const cacheFilename = `optimized_q${qValue}_${targetFormat}.bin`;
1581
+ const cachePath = path__default.default.join(cacheDir, cacheFilename);
1582
+ if (fs__default.default.existsSync(cachePath)) {
1583
+ const cacheStat = fs__default.default.statSync(cachePath);
1584
+ res.setHeader("Content-Type", `image/${targetFormat}`);
1585
+ res.setHeader("Content-Length", cacheStat.size);
1586
+ res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
1587
+ if (config.cors?.enabled) {
1588
+ res.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
1589
+ }
1590
+ if ("destroy" in stream) stream.destroy();
1591
+ fs__default.default.createReadStream(cachePath).pipe(res);
1592
+ return;
1593
+ }
1594
+ if (!fs__default.default.existsSync(cacheDir)) fs__default.default.mkdirSync(cacheDir, { recursive: true });
1595
+ const pipeline = sharp__default.default();
1596
+ if (targetFormat === "jpeg") {
1597
+ pipeline.jpeg({ quality: qValue, mozjpeg: true });
1598
+ res.setHeader("Content-Type", "image/jpeg");
1599
+ } else if (targetFormat === "png") {
1600
+ pipeline.png({ quality: qValue });
1601
+ res.setHeader("Content-Type", "image/png");
1602
+ } else if (targetFormat === "webp") {
1603
+ pipeline.webp({ quality: qValue });
1604
+ res.setHeader("Content-Type", "image/webp");
1605
+ } else if (targetFormat === "avif") {
1606
+ pipeline.avif({ quality: qValue });
1607
+ res.setHeader("Content-Type", "image/avif");
1608
+ }
1609
+ res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
1610
+ stream.pipe(pipeline);
1611
+ pipeline.clone().toFile(cachePath).catch((e) => console.error("[next-drive] Cache write failed:", e));
1612
+ pipeline.clone().pipe(res);
1613
+ return;
1614
+ } catch (e) {
1615
+ console.error("[next-drive] Image transformation failed:", e);
1616
+ }
1617
+ }
1618
+ res.setHeader("Content-Type", mime);
1532
1619
  if (size) res.setHeader("Content-Length", size);
1533
1620
  stream.pipe(res);
1534
1621
  return;
@@ -2074,5 +2161,5 @@ exports.driveReadFile = driveReadFile;
2074
2161
  exports.driveUpload = driveUpload;
2075
2162
  exports.getDriveConfig = getDriveConfig;
2076
2163
  exports.getDriveInformation = getDriveInformation;
2077
- //# sourceMappingURL=chunk-25MNL2OG.cjs.map
2078
- //# sourceMappingURL=chunk-25MNL2OG.cjs.map
2164
+ //# sourceMappingURL=chunk-KQGZXSKY.cjs.map
2165
+ //# sourceMappingURL=chunk-KQGZXSKY.cjs.map