@muhgholy/next-drive 3.8.0 → 3.10.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.
package/README.md CHANGED
@@ -28,6 +28,46 @@ npm install @muhgholy/next-drive
28
28
  | React | >= 18 |
29
29
  | Mongoose | >= 7 |
30
30
  | Tailwind CSS | >= 3 |
31
+ | TypeScript | >= 5 |
32
+
33
+ **TypeScript Configuration:**
34
+
35
+ This package uses [subpath exports](https://nodejs.org/api/packages.html#subpath-exports). Configure your `tsconfig.json` based on your project type:
36
+
37
+ **For Next.js (App Router or Pages Router):**
38
+
39
+ ```json
40
+ {
41
+ "compilerOptions": {
42
+ "module": "esnext",
43
+ "moduleResolution": "bundler"
44
+ }
45
+ }
46
+ ```
47
+
48
+ **For Node.js/Express servers:**
49
+
50
+ ```json
51
+ {
52
+ "compilerOptions": {
53
+ "module": "nodenext",
54
+ "moduleResolution": "nodenext"
55
+ }
56
+ }
57
+ ```
58
+
59
+ **For projects using bundlers (Vite, Webpack, etc.):**
60
+
61
+ ```json
62
+ {
63
+ "compilerOptions": {
64
+ "module": "esnext",
65
+ "moduleResolution": "bundler"
66
+ }
67
+ }
68
+ ```
69
+
70
+ > ⚠️ The legacy `"moduleResolution": "node"` is **not supported** and will cause build errors with subpath imports like `@muhgholy/next-drive/server`.
31
71
 
32
72
  **FFmpeg** (for video thumbnails):
33
73
 
@@ -1,4 +1,3 @@
1
- import { __require } from './chunk-DGUM43GV.js';
2
1
  import formidable from 'formidable';
3
2
  import path3 from 'path';
4
3
  import fs4 from 'fs';
@@ -10,30 +9,41 @@ import { z } from 'zod';
10
9
  import ffmpeg from 'fluent-ffmpeg';
11
10
  import { google } from 'googleapis';
12
11
 
12
+ // src/server/index.ts
13
13
  var globalConfig = null;
14
14
  var driveConfiguration = (config) => {
15
15
  if (mongoose2.connection.readyState !== 1) {
16
16
  throw new Error("Database not connected. Please connect to Mongoose before initializing next-drive.");
17
17
  }
18
- const mergedConfig = {
19
- ...config,
20
- security: {
21
- maxUploadSizeInBytes: config.security?.maxUploadSizeInBytes ?? 10 * 1024 * 1024,
22
- // Default to 10MB
23
- allowedMimeTypes: config.security?.allowedMimeTypes ?? ["*/*"],
24
- signedUrls: config.security?.signedUrls,
25
- trash: config.security?.trash
26
- },
27
- information: config.information ?? (async (req) => {
28
- return {
29
- key: { id: "default-user" },
30
- storage: { quotaInBytes: 10 * 1024 * 1024 * 1024 }
31
- // Default to 10GB
32
- };
33
- })
34
- };
35
- globalConfig = mergedConfig;
36
- return mergedConfig;
18
+ const mode = config.mode || "NORMAL";
19
+ if (mode === "ROOT") {
20
+ globalConfig = {
21
+ ...config,
22
+ mode: "ROOT",
23
+ security: config.security || {
24
+ maxUploadSizeInBytes: 1024 * 1024 * 1024 * 10,
25
+ // 10GB default for ROOT
26
+ allowedMimeTypes: ["*/*"]
27
+ }
28
+ };
29
+ return globalConfig;
30
+ } else {
31
+ if (!config.information) {
32
+ throw new Error("information callback is required in NORMAL mode");
33
+ }
34
+ globalConfig = {
35
+ ...config,
36
+ mode: "NORMAL",
37
+ security: {
38
+ maxUploadSizeInBytes: config.security?.maxUploadSizeInBytes ?? 10 * 1024 * 1024,
39
+ allowedMimeTypes: config.security?.allowedMimeTypes ?? ["*/*"],
40
+ signedUrls: config.security?.signedUrls,
41
+ trash: config.security?.trash
42
+ },
43
+ information: config.information
44
+ };
45
+ return globalConfig;
46
+ }
37
47
  };
38
48
  var getDriveConfig = () => {
39
49
  if (!globalConfig) throw new Error("Drive configuration not initialized");
@@ -41,6 +51,16 @@ var getDriveConfig = () => {
41
51
  };
42
52
  var getDriveInformation = async (req) => {
43
53
  const config = getDriveConfig();
54
+ if (config.mode === "ROOT") {
55
+ if (!config.information) {
56
+ return {
57
+ key: null,
58
+ storage: { quotaInBytes: Number.MAX_SAFE_INTEGER }
59
+ // Unlimited quota in ROOT mode
60
+ };
61
+ }
62
+ return config.information(req);
63
+ }
44
64
  return config.information(req);
45
65
  };
46
66
  var informationSchema = new Schema({
@@ -252,19 +272,25 @@ var LocalStorageProvider = {
252
272
  search: async (query, owner, accountId) => {
253
273
  },
254
274
  getQuota: async (owner, accountId, configuredQuotaInBytes) => {
275
+ const config = getDriveConfig();
276
+ const isRootMode = config.mode === "ROOT";
277
+ const match = {
278
+ "information.type": "FILE",
279
+ trashedAt: null,
280
+ "provider.type": "LOCAL",
281
+ storageAccountId: accountId || null
282
+ };
283
+ if (!isRootMode) {
284
+ match.owner = owner;
285
+ }
255
286
  const result = await drive_default.aggregate([
256
- {
257
- $match: {
258
- owner,
259
- "information.type": "FILE",
260
- trashedAt: null,
261
- "provider.type": "LOCAL",
262
- storageAccountId: accountId || null
263
- }
264
- },
287
+ { $match: match },
265
288
  { $group: { _id: null, total: { $sum: "$information.sizeInBytes" } } }
266
289
  ]);
267
290
  const usedInBytes = result[0]?.total || 0;
291
+ if (isRootMode) {
292
+ return { usedInBytes, quotaInBytes: Number.MAX_SAFE_INTEGER };
293
+ }
268
294
  return { usedInBytes, quotaInBytes: configuredQuotaInBytes ?? 0 };
269
295
  },
270
296
  openStream: async (item, accountId) => {
@@ -606,6 +632,23 @@ var GoogleDriveProvider = {
606
632
  }
607
633
  },
608
634
  getQuota: async (owner, accountId, _configuredQuotaInBytes) => {
635
+ const config = getDriveConfig();
636
+ const isRootMode = config.mode === "ROOT";
637
+ if (isRootMode) {
638
+ const result = await drive_default.aggregate([
639
+ {
640
+ $match: {
641
+ "information.type": "FILE",
642
+ trashedAt: null,
643
+ "provider.type": "GOOGLE",
644
+ storageAccountId: accountId || null
645
+ }
646
+ },
647
+ { $group: { _id: null, total: { $sum: "$information.sizeInBytes" } } }
648
+ ]);
649
+ const usedInBytes = result[0]?.total || 0;
650
+ return { usedInBytes, quotaInBytes: Number.MAX_SAFE_INTEGER };
651
+ }
609
652
  try {
610
653
  const { client } = await createAuthClient(owner, accountId);
611
654
  const drive = google.drive({ version: "v3", auth: client });
@@ -832,7 +875,7 @@ var getNextOrderValue = async (owner) => {
832
875
  };
833
876
  var driveGetUrl = (fileId, options) => {
834
877
  const config = getDriveConfig();
835
- if (!config.security.signedUrls?.enabled) {
878
+ if (!config.security?.signedUrls?.enabled) {
836
879
  return `/api/drive?action=serve&id=${fileId}`;
837
880
  }
838
881
  const { secret, expiresIn } = config.security.signedUrls;
@@ -1119,13 +1162,14 @@ var driveUpload = async (source, key, options) => {
1119
1162
  };
1120
1163
  mimeType = mimeTypes[ext] || "application/octet-stream";
1121
1164
  }
1122
- if (!validateMimeType(mimeType, config.security.allowedMimeTypes)) {
1165
+ if (config.security && !validateMimeType(mimeType, config.security.allowedMimeTypes)) {
1123
1166
  throw new Error(`File type ${mimeType} not allowed`);
1124
1167
  }
1125
- if (fileSize > config.security.maxUploadSizeInBytes) {
1168
+ if (config.security && fileSize > config.security.maxUploadSizeInBytes) {
1126
1169
  throw new Error(`File size ${fileSize} exceeds maximum allowed size ${config.security.maxUploadSizeInBytes}`);
1127
1170
  }
1128
- if (!options.enforce) {
1171
+ const isRootMode = config.mode === "ROOT";
1172
+ if (!options.enforce && !isRootMode) {
1129
1173
  const quota = await provider.getQuota(key, accountId, void 0);
1130
1174
  if (quota.usedInBytes + fileSize > quota.quotaInBytes) {
1131
1175
  throw new Error("Storage quota exceeded");
@@ -1251,7 +1295,7 @@ var driveAPIHandler = async (req, res) => {
1251
1295
  }
1252
1296
  const drive = await drive_default.findById(id);
1253
1297
  if (!drive) return res.status(404).json({ status: 404, message: "File not found" });
1254
- if (config.security.signedUrls?.enabled) {
1298
+ if (config.security?.signedUrls?.enabled) {
1255
1299
  if (!token || typeof token !== "string") {
1256
1300
  return res.status(401).json({ status: 401, message: "Missing or invalid token" });
1257
1301
  }
@@ -1303,9 +1347,11 @@ var driveAPIHandler = async (req, res) => {
1303
1347
  }
1304
1348
  }
1305
1349
  try {
1350
+ const mode = config.mode || "NORMAL";
1306
1351
  const information = await getDriveInformation(req);
1307
1352
  const { key: owner } = information;
1308
1353
  const STORAGE_PATH = config.storage.path;
1354
+ const isRootMode = mode === "ROOT";
1309
1355
  if (action === "information") {
1310
1356
  const { clientId, clientSecret, redirectUri } = config.storage?.google || {};
1311
1357
  const googleConfigured = !!(clientId && clientSecret && redirectUri);
@@ -1315,7 +1361,8 @@ var driveAPIHandler = async (req, res) => {
1315
1361
  data: {
1316
1362
  providers: {
1317
1363
  google: googleConfigured
1318
- }
1364
+ },
1365
+ mode
1319
1366
  }
1320
1367
  });
1321
1368
  }
@@ -1326,10 +1373,9 @@ var driveAPIHandler = async (req, res) => {
1326
1373
  if (provider2 === "GOOGLE") {
1327
1374
  const { clientId, clientSecret, redirectUri } = config.storage?.google || {};
1328
1375
  if (!clientId || !clientSecret || !redirectUri) return res.status(500).json({ status: 500, message: "Google not configured" });
1329
- const { google: google2 } = __require("googleapis");
1330
1376
  const callbackUri = new URL(redirectUri);
1331
1377
  callbackUri.searchParams.set("action", "callback");
1332
- const oAuth2Client = new google2.auth.OAuth2(clientId, clientSecret, callbackUri.toString());
1378
+ const oAuth2Client = new google.auth.OAuth2(clientId, clientSecret, callbackUri.toString());
1333
1379
  const state = Buffer.from(JSON.stringify({ owner })).toString("base64");
1334
1380
  const url = oAuth2Client.generateAuthUrl({
1335
1381
  access_type: "offline",
@@ -1347,13 +1393,12 @@ var driveAPIHandler = async (req, res) => {
1347
1393
  if (!code) return res.status(400).json({ status: 400, message: "Missing code" });
1348
1394
  const { clientId, clientSecret, redirectUri } = config.storage?.google || {};
1349
1395
  if (!clientId || !clientSecret || !redirectUri) return res.status(500).json({ status: 500, message: "Google not configured" });
1350
- const { google: google2 } = __require("googleapis");
1351
1396
  const callbackUri = new URL(redirectUri);
1352
1397
  callbackUri.searchParams.set("action", "callback");
1353
- const oAuth2Client = new google2.auth.OAuth2(clientId, clientSecret, callbackUri.toString());
1398
+ const oAuth2Client = new google.auth.OAuth2(clientId, clientSecret, callbackUri.toString());
1354
1399
  const { tokens } = await oAuth2Client.getToken(code);
1355
1400
  oAuth2Client.setCredentials(tokens);
1356
- const oauth2 = google2.oauth2({ version: "v2", auth: oAuth2Client });
1401
+ const oauth2 = google.oauth2({ version: "v2", auth: oAuth2Client });
1357
1402
  const userInfo = await oauth2.userinfo.get();
1358
1403
  const existing = await account_default.findOne({ owner, "metadata.google.email": userInfo.data.email, "metadata.provider": "GOOGLE" });
1359
1404
  if (existing) {
@@ -1361,7 +1406,7 @@ var driveAPIHandler = async (req, res) => {
1361
1406
  existing.markModified("metadata");
1362
1407
  await existing.save();
1363
1408
  } else {
1364
- await account_default.create({
1409
+ const newAccount = new account_default({
1365
1410
  owner,
1366
1411
  name: userInfo.data.name || "Google Drive",
1367
1412
  metadata: {
@@ -1372,6 +1417,7 @@ var driveAPIHandler = async (req, res) => {
1372
1417
  }
1373
1418
  }
1374
1419
  });
1420
+ await newAccount.save();
1375
1421
  }
1376
1422
  res.setHeader("Content-Type", "text/html");
1377
1423
  return res.send(`<!DOCTYPE html>
@@ -1449,12 +1495,14 @@ var driveAPIHandler = async (req, res) => {
1449
1495
  console.error("Sync failed", e);
1450
1496
  }
1451
1497
  const query = {
1452
- owner,
1453
1498
  "provider.type": provider.name,
1454
1499
  storageAccountId: accountId || null,
1455
1500
  parentId: folderId === "root" || !folderId ? null : folderId,
1456
1501
  trashedAt: null
1457
1502
  };
1503
+ if (!isRootMode) {
1504
+ query.owner = owner;
1505
+ }
1458
1506
  if (afterId) query._id = { $lt: afterId };
1459
1507
  const items = await drive_default.find(query, {}, { sort: { order: 1, _id: -1 }, limit });
1460
1508
  const plainItems = await Promise.all(items.map((item) => item.toClient()));
@@ -1474,12 +1522,14 @@ var driveAPIHandler = async (req, res) => {
1474
1522
  }
1475
1523
  }
1476
1524
  const query = {
1477
- owner,
1478
1525
  "provider.type": provider.name,
1479
1526
  storageAccountId: accountId || null,
1480
1527
  trashedAt: trashed ? { $ne: null } : null,
1481
1528
  name: { $regex: q, $options: "i" }
1482
1529
  };
1530
+ if (!isRootMode) {
1531
+ query.owner = owner;
1532
+ }
1483
1533
  if (folderId && folderId !== "root") query.parentId = folderId;
1484
1534
  const items = await drive_default.find(query, {}, { limit, sort: { createdAt: -1 } });
1485
1535
  const plainItems = await Promise.all(items.map((i) => i.toClient()));
@@ -1492,7 +1542,7 @@ var driveAPIHandler = async (req, res) => {
1492
1542
  if (!fs4.existsSync(systemTmpDir)) fs4.mkdirSync(systemTmpDir, { recursive: true });
1493
1543
  const form = formidable({
1494
1544
  multiples: false,
1495
- maxFileSize: config.security.maxUploadSizeInBytes * 2,
1545
+ maxFileSize: (config.security?.maxUploadSizeInBytes ?? 1024 * 1024 * 1024) * 2,
1496
1546
  uploadDir: systemTmpDir,
1497
1547
  keepExtensions: true
1498
1548
  });
@@ -1527,14 +1577,18 @@ var driveAPIHandler = async (req, res) => {
1527
1577
  const tempBaseDir = path3.join(os2.tmpdir(), "next-drive-uploads");
1528
1578
  if (!currentUploadId) {
1529
1579
  if (chunkIndex !== 0) return res.status(400).json({ message: "Missing upload ID for non-zero chunk" });
1530
- if (fileType && !validateMimeType(fileType, config.security.allowedMimeTypes)) {
1531
- cleanupTempFiles(files);
1532
- return res.status(400).json({ status: 400, message: `File type ${fileType} not allowed` });
1580
+ if (fileType && config.security) {
1581
+ if (!validateMimeType(fileType, config.security.allowedMimeTypes)) {
1582
+ cleanupTempFiles(files);
1583
+ return res.status(400).json({ status: 400, message: `File type ${fileType} not allowed` });
1584
+ }
1533
1585
  }
1534
- const quota = await provider.getQuota(owner, accountId, information.storage.quotaInBytes);
1535
- if (quota.usedInBytes + fileSizeInBytes > quota.quotaInBytes) {
1536
- cleanupTempFiles(files);
1537
- return res.status(413).json({ status: 413, message: "Storage quota exceeded" });
1586
+ if (!isRootMode) {
1587
+ const quota = await provider.getQuota(owner, accountId, information.storage.quotaInBytes);
1588
+ if (quota.usedInBytes + fileSizeInBytes > quota.quotaInBytes) {
1589
+ cleanupTempFiles(files);
1590
+ return res.status(413).json({ status: 413, message: "Storage quota exceeded" });
1591
+ }
1538
1592
  }
1539
1593
  currentUploadId = crypto2.randomUUID();
1540
1594
  const uploadDir = path3.join(tempBaseDir, currentUploadId);
@@ -1799,5 +1853,5 @@ var driveAPIHandler = async (req, res) => {
1799
1853
  };
1800
1854
 
1801
1855
  export { driveAPIHandler, driveConfiguration, driveDelete, driveFilePath, driveFileSchemaZod, driveGetUrl, driveInfo, driveList, driveReadFile, driveUpload, getDriveConfig, getDriveInformation };
1802
- //# sourceMappingURL=chunk-RQ3YRDNR.js.map
1803
- //# sourceMappingURL=chunk-RQ3YRDNR.js.map
1856
+ //# sourceMappingURL=chunk-32UNO3KE.js.map
1857
+ //# sourceMappingURL=chunk-32UNO3KE.js.map