mbkbucket 4.0.0 → 4.1.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/.env.example +16 -15
- package/README.md +613 -561
- package/index.js +36 -33
- package/lib/cli.js +239 -239
- package/lib/public/bucketadmin.css +2042 -2042
- package/lib/public/bucketadmin.js +1844 -1622
- package/lib/public/mbkbucket-helper-demo.html +0 -58
- package/lib/public/mbkbucket-helper.js +1 -9
- package/lib/src/config/validation.js +91 -86
- package/lib/src/controllers/api.controller.js +486 -459
- package/lib/src/controllers/dashboard.controller.js +69 -69
- package/lib/src/controllers/info.controller.js +13 -13
- package/lib/src/controllers/view.controller.js +234 -233
- package/lib/src/core/metrics.js +134 -0
- package/lib/src/core/storage/capabilities.js +58 -0
- package/lib/src/core/storage/errors.js +0 -5
- package/lib/src/core/storage/gdrive-auth.js +0 -36
- package/lib/src/core/storage/gdrive-provider.js +101 -43
- package/lib/src/core/storage/index.js +4 -1
- package/lib/src/core/storage/local-provider.js +507 -0
- package/lib/src/core/storage/models.js +90 -50
- package/lib/src/core/storage/path-strategy.js +14 -1
- package/lib/src/core/storage/s3-provider.js +17 -37
- package/lib/src/core/storage/storage-manager.js +70 -50
- package/lib/src/core/storage/storage-provider.js +49 -120
- package/lib/src/middleware/bucket-resolver.js +37 -37
- package/lib/src/routes/api.routes.js +4 -1
- package/lib/src/routes/index.js +60 -60
- package/lib/src/routes/view.routes.js +28 -28
- package/lib/src/services/s3.service.js +42 -42
- package/lib/src/services/storage.service.js +311 -213
- package/lib/src/utils/cleanup-scheduler.js +91 -0
- package/lib/src/utils/errors.js +56 -56
- package/lib/src/utils/helpers.js +6 -6
- package/package.json +3 -5
- package/views/bucket.hbs +0 -5
- package/views/bucketadmin_move_modal.hbs +35 -35
- package/views/bucketadmin_preview_modal.hbs +108 -108
- package/views/bucketadmincontent.hbs +193 -193
- package/views/mbkbucket_info.hbs +134 -134
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MBKBucket - Storage Provider Capabilities
|
|
3
|
+
* Copyright (c) 2026 Muhammad Bin Khalid, MBKTech.org and contributors
|
|
4
|
+
* Licensed under the MIT License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { StorageUnsupportedOperationError } from "./errors.js";
|
|
8
|
+
|
|
9
|
+
export class StorageCapabilities {
|
|
10
|
+
constructor(flags = {}) {
|
|
11
|
+
this.multipart = Boolean(flags.multipart);
|
|
12
|
+
this.resumable = Boolean(flags.resumable ?? flags.multipart);
|
|
13
|
+
this.nativeFolders = Boolean(flags.nativeFolders);
|
|
14
|
+
this.ranges = Boolean(flags.ranges ?? true);
|
|
15
|
+
this.copy = Boolean(flags.copy);
|
|
16
|
+
this.move = Boolean(flags.move);
|
|
17
|
+
this.search = Boolean(flags.search);
|
|
18
|
+
this.directDownloadUrl = Boolean(flags.directDownloadUrl);
|
|
19
|
+
this.versioning = Boolean(flags.versioning);
|
|
20
|
+
this.publicUrls = Boolean(flags.publicUrls);
|
|
21
|
+
this.storageQuota = Boolean(flags.storageQuota);
|
|
22
|
+
this.deleteBatch = Boolean(flags.deleteBatch ?? true);
|
|
23
|
+
this.metadata = Boolean(flags.metadata ?? true);
|
|
24
|
+
|
|
25
|
+
Object.freeze(this);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
has(capability) {
|
|
29
|
+
return Boolean(this[capability]);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
assert(capability, operationName, providerType = 'storage provider') {
|
|
33
|
+
if (!this.has(capability)) {
|
|
34
|
+
const op = operationName || capability;
|
|
35
|
+
throw new StorageUnsupportedOperationError(
|
|
36
|
+
`Operation '${op}' is not supported by ${providerType} (missing capability: ${capability})`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
toJSON() {
|
|
42
|
+
return {
|
|
43
|
+
multipart: this.multipart,
|
|
44
|
+
resumable: this.resumable,
|
|
45
|
+
nativeFolders: this.nativeFolders,
|
|
46
|
+
ranges: this.ranges,
|
|
47
|
+
copy: this.copy,
|
|
48
|
+
move: this.move,
|
|
49
|
+
search: this.search,
|
|
50
|
+
directDownloadUrl: this.directDownloadUrl,
|
|
51
|
+
versioning: this.versioning,
|
|
52
|
+
publicUrls: this.publicUrls,
|
|
53
|
+
storageQuota: this.storageQuota,
|
|
54
|
+
deleteBatch: this.deleteBatch,
|
|
55
|
+
metadata: this.metadata
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -5,11 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
export class StorageError extends Error {
|
|
8
|
-
/**
|
|
9
|
-
* @param {string} message
|
|
10
|
-
* @param {number} [status=500]
|
|
11
|
-
* @param {string} [code='INTERNAL_STORAGE_ERROR']
|
|
12
|
-
*/
|
|
13
8
|
constructor(message, status = 500, code = 'INTERNAL_STORAGE_ERROR') {
|
|
14
9
|
super(message);
|
|
15
10
|
this.name = this.constructor.name;
|
|
@@ -15,17 +15,6 @@ export const GOOGLE_DRIVE_SCOPES = [
|
|
|
15
15
|
'https://www.googleapis.com/auth/drive.metadata.readonly'
|
|
16
16
|
];
|
|
17
17
|
|
|
18
|
-
/**
|
|
19
|
-
* Generate Google OAuth 2.0 Authorization URL for user consent.
|
|
20
|
-
* @param {object} options
|
|
21
|
-
* @param {string} options.clientId
|
|
22
|
-
* @param {string} options.redirectUri
|
|
23
|
-
* @param {string} [options.state]
|
|
24
|
-
* @param {string[]} [options.scopes]
|
|
25
|
-
* @param {'consent'|'select_account'|'none'} [options.prompt='consent']
|
|
26
|
-
* @param {'offline'|'online'} [options.accessType='offline']
|
|
27
|
-
* @returns {string}
|
|
28
|
-
*/
|
|
29
18
|
export function getGoogleAuthUrl({
|
|
30
19
|
clientId,
|
|
31
20
|
redirectUri,
|
|
@@ -51,15 +40,6 @@ export function getGoogleAuthUrl({
|
|
|
51
40
|
return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
|
|
52
41
|
}
|
|
53
42
|
|
|
54
|
-
/**
|
|
55
|
-
* Exchange an OAuth authorization code for tokens (access_token, refresh_token).
|
|
56
|
-
* @param {object} options
|
|
57
|
-
* @param {string} options.code
|
|
58
|
-
* @param {string} options.clientId
|
|
59
|
-
* @param {string} options.clientSecret
|
|
60
|
-
* @param {string} options.redirectUri
|
|
61
|
-
* @returns {Promise<{ access_token: string, refresh_token?: string, expires_in: number, token_type: string, scope: string, expiry_date: number }>}
|
|
62
|
-
*/
|
|
63
43
|
export async function exchangeCodeForTokens({
|
|
64
44
|
code,
|
|
65
45
|
clientId,
|
|
@@ -94,14 +74,6 @@ export async function exchangeCodeForTokens({
|
|
|
94
74
|
return { ...data, expiry_date };
|
|
95
75
|
}
|
|
96
76
|
|
|
97
|
-
/**
|
|
98
|
-
* Refresh an expired access token using a refresh token.
|
|
99
|
-
* @param {object} options
|
|
100
|
-
* @param {string} options.clientId
|
|
101
|
-
* @param {string} options.clientSecret
|
|
102
|
-
* @param {string} options.refreshToken
|
|
103
|
-
* @returns {Promise<{ access_token: string, expires_in: number, expiry_date: number, token_type: string }>}
|
|
104
|
-
*/
|
|
105
77
|
export async function refreshGoogleAccessToken({
|
|
106
78
|
clientId,
|
|
107
79
|
clientSecret,
|
|
@@ -134,14 +106,6 @@ export async function refreshGoogleAccessToken({
|
|
|
134
106
|
return { ...data, expiry_date };
|
|
135
107
|
}
|
|
136
108
|
|
|
137
|
-
/**
|
|
138
|
-
* Generate an OAuth2 access token from a Google Service Account using JWT signing.
|
|
139
|
-
* @param {object} options
|
|
140
|
-
* @param {string} options.clientEmail
|
|
141
|
-
* @param {string} options.privateKey
|
|
142
|
-
* @param {string[]} [options.scopes]
|
|
143
|
-
* @returns {Promise<{ access_token: string, expires_in: number, expiry_date: number }>}
|
|
144
|
-
*/
|
|
145
109
|
export async function getServiceAccountAccessToken({
|
|
146
110
|
clientEmail,
|
|
147
111
|
privateKey,
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { Readable } from "node:stream";
|
|
8
8
|
import { StorageProvider } from "./storage-provider.js";
|
|
9
|
+
import { StorageCapabilities } from "./capabilities.js";
|
|
9
10
|
import { StorageFile, StorageFolder, StorageListResult } from "./models.js";
|
|
10
11
|
import { StorageNotFoundError, StorageConflictError, StorageAccessDeniedError, StorageValidationError, StorageConfigError, StorageError } from "./errors.js";
|
|
11
12
|
import { refreshGoogleAccessToken, getServiceAccountAccessToken } from "./gdrive-auth.js";
|
|
@@ -19,19 +20,49 @@ function escapeGdriveQuery(str = '') {
|
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export class GoogleDriveStorageProvider extends StorageProvider {
|
|
22
|
-
/**
|
|
23
|
-
* @param {string} name - Connection identifier
|
|
24
|
-
* @param {object} config - Configuration object
|
|
25
|
-
*/
|
|
26
23
|
constructor(name, config = {}) {
|
|
27
24
|
super(name, config);
|
|
28
25
|
this.rootFolderId = config.folder_id || config.root_folder_id || 'root';
|
|
29
26
|
this._accessToken = config.access_token || null;
|
|
30
27
|
this._tokenExpiry = config.expiry_date || 0;
|
|
31
28
|
this._folderCache = new Map();
|
|
32
|
-
this.
|
|
33
|
-
this.
|
|
34
|
-
this.
|
|
29
|
+
this._setFolderCache('', this.rootFolderId);
|
|
30
|
+
this._setFolderCache('.', this.rootFolderId);
|
|
31
|
+
this._setFolderCache('/', this.rootFolderId);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_setFolderCache(path, id) {
|
|
35
|
+
if (this._folderCache.size >= 500) {
|
|
36
|
+
const oldestKey = this._folderCache.keys().next().value;
|
|
37
|
+
this._folderCache.delete(oldestKey);
|
|
38
|
+
}
|
|
39
|
+
this._folderCache.set(path, { id, expires: Date.now() + 300000 });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
_getFolderCache(path) {
|
|
43
|
+
const entry = this._folderCache.get(path);
|
|
44
|
+
if (!entry) return null;
|
|
45
|
+
if (typeof entry === 'object' && entry.expires && entry.expires < Date.now()) {
|
|
46
|
+
this._folderCache.delete(path);
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
return typeof entry === 'object' && entry.id ? entry.id : entry;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
_invalidateFolderCache(prefix = '') {
|
|
53
|
+
if (!prefix) {
|
|
54
|
+
this._folderCache.clear();
|
|
55
|
+
this._setFolderCache('', this.rootFolderId);
|
|
56
|
+
this._setFolderCache('.', this.rootFolderId);
|
|
57
|
+
this._setFolderCache('/', this.rootFolderId);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
this._folderCache.delete(prefix);
|
|
61
|
+
for (const k of this._folderCache.keys()) {
|
|
62
|
+
if (k.startsWith(`${prefix}/`)) {
|
|
63
|
+
this._folderCache.delete(k);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
35
66
|
}
|
|
36
67
|
|
|
37
68
|
get type() {
|
|
@@ -39,16 +70,19 @@ export class GoogleDriveStorageProvider extends StorageProvider {
|
|
|
39
70
|
}
|
|
40
71
|
|
|
41
72
|
get capabilities() {
|
|
42
|
-
return {
|
|
73
|
+
return new StorageCapabilities({
|
|
43
74
|
multipart: false,
|
|
44
|
-
|
|
75
|
+
resumable: false,
|
|
45
76
|
nativeFolders: true,
|
|
46
77
|
ranges: true,
|
|
47
78
|
copy: true,
|
|
48
79
|
move: true,
|
|
49
80
|
search: true,
|
|
50
|
-
directDownloadUrl: true
|
|
51
|
-
|
|
81
|
+
directDownloadUrl: true,
|
|
82
|
+
versioning: false,
|
|
83
|
+
publicUrls: true,
|
|
84
|
+
metadata: true
|
|
85
|
+
});
|
|
52
86
|
}
|
|
53
87
|
|
|
54
88
|
async getAccessToken() {
|
|
@@ -102,29 +136,45 @@ export class GoogleDriveStorageProvider extends StorageProvider {
|
|
|
102
136
|
}
|
|
103
137
|
|
|
104
138
|
async _fetch(url, options = {}) {
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
139
|
+
const maxRetries = options.maxRetries ?? 3;
|
|
140
|
+
let attempt = 0;
|
|
141
|
+
|
|
142
|
+
while (attempt <= maxRetries) {
|
|
143
|
+
const token = await this.getAccessToken();
|
|
144
|
+
const headers = new Headers(options.headers || {});
|
|
145
|
+
headers.set('Authorization', `Bearer ${token}`);
|
|
146
|
+
|
|
147
|
+
let response = await fetch(url, { ...options, headers });
|
|
148
|
+
|
|
149
|
+
// Handle 401 token expiration retry once
|
|
150
|
+
if (response.status === 401 && attempt === 0 && (this.config.refresh_token || this.config.client_email)) {
|
|
151
|
+
debugGDrive('Received 401 from Google Drive API, retrying with refreshed token...');
|
|
152
|
+
this._accessToken = null;
|
|
153
|
+
this._tokenExpiry = 0;
|
|
154
|
+
const newToken = await this.getAccessToken();
|
|
155
|
+
headers.set('Authorization', `Bearer ${newToken}`);
|
|
156
|
+
response = await fetch(url, { ...options, headers });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Handle 429 Too Many Requests or 503/500 temporary service errors with exponential backoff
|
|
160
|
+
if ((response.status === 429 || response.status === 503 || response.status === 500) && attempt < maxRetries) {
|
|
161
|
+
attempt++;
|
|
162
|
+
const retryAfter = Number(response.headers.get('retry-after')) || 0;
|
|
163
|
+
const delayMs = retryAfter > 0 ? retryAfter * 1000 : Math.min(300 * (2 ** (attempt - 1)) + Math.random() * 150, 4000);
|
|
164
|
+
debugGDrive('Google Drive API rate limit/error (%s). Retrying attempt %s in %sms...', response.status, attempt, Math.round(delayMs));
|
|
165
|
+
await new Promise(r => setTimeout(r, delayMs));
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
120
168
|
|
|
121
|
-
|
|
169
|
+
return response;
|
|
170
|
+
}
|
|
122
171
|
}
|
|
123
172
|
|
|
124
173
|
async _resolvePathToFolderId(path = '') {
|
|
125
174
|
const cleanPath = trimSlashes(path);
|
|
126
175
|
if (!cleanPath) return this.rootFolderId;
|
|
127
|
-
|
|
176
|
+
const cached = this._getFolderCache(cleanPath);
|
|
177
|
+
if (cached) return cached;
|
|
128
178
|
|
|
129
179
|
const segments = cleanPath.split('/').filter(Boolean);
|
|
130
180
|
let currentParentId = this.rootFolderId;
|
|
@@ -132,8 +182,9 @@ export class GoogleDriveStorageProvider extends StorageProvider {
|
|
|
132
182
|
|
|
133
183
|
for (const segment of segments) {
|
|
134
184
|
currentPath = currentPath ? `${currentPath}/${segment}` : segment;
|
|
135
|
-
|
|
136
|
-
|
|
185
|
+
const segCached = this._getFolderCache(currentPath);
|
|
186
|
+
if (segCached) {
|
|
187
|
+
currentParentId = segCached;
|
|
137
188
|
continue;
|
|
138
189
|
}
|
|
139
190
|
|
|
@@ -151,7 +202,7 @@ export class GoogleDriveStorageProvider extends StorageProvider {
|
|
|
151
202
|
}
|
|
152
203
|
|
|
153
204
|
currentParentId = data.files[0].id;
|
|
154
|
-
this.
|
|
205
|
+
this._setFolderCache(currentPath, currentParentId);
|
|
155
206
|
}
|
|
156
207
|
|
|
157
208
|
return currentParentId;
|
|
@@ -160,7 +211,8 @@ export class GoogleDriveStorageProvider extends StorageProvider {
|
|
|
160
211
|
async _ensureFolderPath(path = '') {
|
|
161
212
|
const cleanPath = trimSlashes(path);
|
|
162
213
|
if (!cleanPath) return this.rootFolderId;
|
|
163
|
-
|
|
214
|
+
const cached = this._getFolderCache(cleanPath);
|
|
215
|
+
if (cached) return cached;
|
|
164
216
|
|
|
165
217
|
const segments = cleanPath.split('/').filter(Boolean);
|
|
166
218
|
let currentParentId = this.rootFolderId;
|
|
@@ -168,8 +220,9 @@ export class GoogleDriveStorageProvider extends StorageProvider {
|
|
|
168
220
|
|
|
169
221
|
for (const segment of segments) {
|
|
170
222
|
currentPath = currentPath ? `${currentPath}/${segment}` : segment;
|
|
171
|
-
|
|
172
|
-
|
|
223
|
+
const segCached = this._getFolderCache(currentPath);
|
|
224
|
+
if (segCached) {
|
|
225
|
+
currentParentId = segCached;
|
|
173
226
|
continue;
|
|
174
227
|
}
|
|
175
228
|
|
|
@@ -202,7 +255,7 @@ export class GoogleDriveStorageProvider extends StorageProvider {
|
|
|
202
255
|
currentParentId = createData.id;
|
|
203
256
|
}
|
|
204
257
|
|
|
205
|
-
this.
|
|
258
|
+
this._setFolderCache(currentPath, currentParentId);
|
|
206
259
|
}
|
|
207
260
|
|
|
208
261
|
return currentParentId;
|
|
@@ -304,7 +357,11 @@ export class GoogleDriveStorageProvider extends StorageProvider {
|
|
|
304
357
|
for (const item of (data.files || [])) {
|
|
305
358
|
if (item.mimeType === 'application/vnd.google-apps.folder') {
|
|
306
359
|
const folderPrefix = `${prefixPath}${item.name}/`;
|
|
307
|
-
commonPrefixes.push(new StorageFolder(
|
|
360
|
+
commonPrefixes.push(new StorageFolder({
|
|
361
|
+
prefix: folderPrefix,
|
|
362
|
+
id: item.id,
|
|
363
|
+
provider: 'gdrive'
|
|
364
|
+
}));
|
|
308
365
|
this._folderCache.set(trimSlashes(folderPrefix), item.id);
|
|
309
366
|
} else {
|
|
310
367
|
const itemKey = `${prefixPath}${item.name}`;
|
|
@@ -312,12 +369,16 @@ export class GoogleDriveStorageProvider extends StorageProvider {
|
|
|
312
369
|
key: itemKey,
|
|
313
370
|
size: parseInt(item.size || 0, 10),
|
|
314
371
|
lastModified: item.modifiedTime || item.createdTime || Date.now(),
|
|
372
|
+
createdAt: item.createdTime,
|
|
315
373
|
etag: item.md5Checksum ? `"${item.md5Checksum}"` : `"${item.id}"`,
|
|
316
374
|
storageClass: 'GOOGLE_DRIVE',
|
|
375
|
+
provider: 'gdrive',
|
|
317
376
|
id: item.id,
|
|
377
|
+
nativeId: item.id,
|
|
318
378
|
mimeType: item.mimeType,
|
|
319
379
|
webViewLink: item.webViewLink,
|
|
320
|
-
webContentLink: item.webContentLink
|
|
380
|
+
webContentLink: item.webContentLink,
|
|
381
|
+
metadata: item.properties || {}
|
|
321
382
|
}));
|
|
322
383
|
}
|
|
323
384
|
}
|
|
@@ -528,12 +589,7 @@ export class GoogleDriveStorageProvider extends StorageProvider {
|
|
|
528
589
|
throw new StorageError(`Failed to delete Google Drive folder: ${data.error?.message || res.statusText}`);
|
|
529
590
|
}
|
|
530
591
|
|
|
531
|
-
this.
|
|
532
|
-
for (const [k] of this._folderCache.entries()) {
|
|
533
|
-
if (k.startsWith(`${cleanPrefix}/`)) {
|
|
534
|
-
this._folderCache.delete(k);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
592
|
+
this._invalidateFolderCache(cleanPrefix);
|
|
537
593
|
|
|
538
594
|
return { deletedCount: 1, prefix: cleanPrefix, deletedAt: nowIso() };
|
|
539
595
|
} catch (error) {
|
|
@@ -587,6 +643,7 @@ export class GoogleDriveStorageProvider extends StorageProvider {
|
|
|
587
643
|
}
|
|
588
644
|
|
|
589
645
|
async copyFile(sourceKey, destKey, _options = {}) {
|
|
646
|
+
this.assertCapability('copy', 'copyFile');
|
|
590
647
|
try {
|
|
591
648
|
const srcClean = trimLeadingSlashes(sourceKey);
|
|
592
649
|
const dstClean = trimLeadingSlashes(destKey);
|
|
@@ -622,6 +679,7 @@ export class GoogleDriveStorageProvider extends StorageProvider {
|
|
|
622
679
|
}
|
|
623
680
|
|
|
624
681
|
async moveFile(sourceKey, destKey, _options = {}) {
|
|
682
|
+
this.assertCapability('move', 'moveFile');
|
|
625
683
|
try {
|
|
626
684
|
const srcClean = trimLeadingSlashes(sourceKey);
|
|
627
685
|
const dstClean = trimLeadingSlashes(destKey);
|
|
@@ -5,10 +5,13 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
export { StorageProvider } from "./storage-provider.js";
|
|
8
|
+
export { StorageCapabilities } from "./capabilities.js";
|
|
8
9
|
export { S3StorageProvider } from "./s3-provider.js";
|
|
9
10
|
export { GoogleDriveStorageProvider } from "./gdrive-provider.js";
|
|
10
|
-
export {
|
|
11
|
+
export { LocalStorageProvider } from "./local-provider.js";
|
|
12
|
+
export { StorageItem, StorageFile, StorageFolder, StorageListResult } from "./models.js";
|
|
11
13
|
export { StorageError, StorageNotFoundError, StorageAccessDeniedError, StorageConflictError, StorageValidationError, StorageConfigError, StorageQuotaExceededError, StorageUnsupportedOperationError } from "./errors.js";
|
|
12
14
|
export { PathStrategy } from "./path-strategy.js";
|
|
13
15
|
export { getGoogleAuthUrl, exchangeCodeForTokens, refreshGoogleAccessToken, getServiceAccountAccessToken, GOOGLE_DRIVE_SCOPES } from "./gdrive-auth.js";
|
|
14
16
|
export { StorageManager, storageManager } from "./storage-manager.js";
|
|
17
|
+
|